summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-03 03:01:18 +0000
committerhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-03 03:01:18 +0000
commitc6206e51a3e587f7eebb3867f835b3e0588055b6 (patch)
tree77a37c71cba678a9414f3d1bec5ead1045a9e308 /media
parent9ff38235dcea4a3a95e25b602c347f5646bdf4bb (diff)
downloadchromium_src-c6206e51a3e587f7eebb3867f835b3e0588055b6.zip
chromium_src-c6206e51a3e587f7eebb3867f835b3e0588055b6.tar.gz
chromium_src-c6206e51a3e587f7eebb3867f835b3e0588055b6.tar.bz2
Cast: Implement cast simulator tool
This is the tool for running a simulated streaming session. The tool runs a cast sender and receiver through a fake network pipe with fake clock and fake task runner. This tool is used for simulation and benchmarking. Also implemented an interrupted poisson process to simulate packet delivery on WiFi. Added a new dependency on the MT19937 library for fast pseudo random number generation. BUG=390682 Review URL: https://codereview.chromium.org/358943003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@281135 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/cast/DEPS1
-rw-r--r--media/cast/cast_testing.gypi4
-rw-r--r--media/cast/receiver/video_decoder.cc7
-rw-r--r--media/cast/rtcp/rtcp.cc7
-rw-r--r--media/cast/test/cast_benchmarks.cc76
-rw-r--r--media/cast/test/fake_media_source.cc2
-rw-r--r--media/cast/test/loopback_transport.cc68
-rw-r--r--media/cast/test/loopback_transport.h55
-rw-r--r--media/cast/test/simulator.cc351
-rw-r--r--media/cast/test/utility/default_config.cc37
-rw-r--r--media/cast/test/utility/default_config.h9
-rw-r--r--media/cast/test/utility/udp_proxy.cc202
-rw-r--r--media/cast/test/utility/udp_proxy.h60
-rw-r--r--media/cast/video_sender/fake_software_video_encoder.cc2
14 files changed, 791 insertions, 90 deletions
diff --git a/media/cast/DEPS b/media/cast/DEPS
index abee286..e44afe0 100644
--- a/media/cast/DEPS
+++ b/media/cast/DEPS
@@ -3,6 +3,7 @@ include_rules = [
"+media",
"+net",
"+third_party/libyuv",
+ "+third_party/mt19937ar",
"+third_party/zlib",
"+ui/gfx",
]
diff --git a/media/cast/cast_testing.gypi b/media/cast/cast_testing.gypi
index e6ebd74..41d3965 100644
--- a/media/cast/cast_testing.gypi
+++ b/media/cast/cast_testing.gypi
@@ -16,6 +16,7 @@
'<(DEPTH)/testing/gtest.gyp:gtest',
'<(DEPTH)/third_party/ffmpeg/ffmpeg.gyp:ffmpeg',
'<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv',
+ '<(DEPTH)/third_party/mt19937ar/mt19937ar.gyp:mt19937ar',
'<(DEPTH)/ui/gfx/gfx.gyp:gfx_geometry',
],
'sources': [
@@ -27,6 +28,8 @@
'test/skewed_single_thread_task_runner.h',
'test/skewed_tick_clock.cc',
'test/skewed_tick_clock.h',
+ 'test/loopback_transport.cc',
+ 'test/loopback_transport.h',
'test/utility/audio_utility.cc',
'test/utility/audio_utility.h',
'test/utility/barcode.cc',
@@ -170,6 +173,7 @@
'dependencies': [
'cast_receiver_app',
'cast_sender_app',
+ 'cast_simulator',
'udp_proxy',
],
},
diff --git a/media/cast/receiver/video_decoder.cc b/media/cast/receiver/video_decoder.cc
index 2fd11fe..896cb0a 100644
--- a/media/cast/receiver/video_decoder.cc
+++ b/media/cast/receiver/video_decoder.cc
@@ -184,9 +184,14 @@ class VideoDecoder::FakeImpl : public VideoDecoder::ImplBase {
virtual ~FakeImpl() {}
virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE {
+ // Make sure this is a JSON string.
+ if (!len || data[0] != '{')
+ return NULL;
base::JSONReader reader;
scoped_ptr<base::Value> values(
- reader.Read(base::StringPiece(reinterpret_cast<char*>(data))));
+ reader.Read(base::StringPiece(reinterpret_cast<char*>(data), len)));
+ if (!values)
+ return NULL;
base::DictionaryValue* dict = NULL;
values->GetAsDictionary(&dict);
diff --git a/media/cast/rtcp/rtcp.cc b/media/cast/rtcp/rtcp.cc
index ddf7fec..74a4689 100644
--- a/media/cast/rtcp/rtcp.cc
+++ b/media/cast/rtcp/rtcp.cc
@@ -5,7 +5,6 @@
#include "media/cast/rtcp/rtcp.h"
#include "base/big_endian.h"
-#include "base/rand_util.h"
#include "media/cast/cast_config.h"
#include "media/cast/cast_defines.h"
#include "media/cast/cast_environment.h"
@@ -387,12 +386,8 @@ bool Rtcp::Rtt(base::TimeDelta* rtt, base::TimeDelta* avg_rtt,
}
void Rtcp::UpdateNextTimeToSendRtcp() {
- int random = base::RandInt(0, 999);
- base::TimeDelta time_to_next =
- (rtcp_interval_ / 2) + (rtcp_interval_ * random / 1000);
-
base::TimeTicks now = cast_environment_->Clock()->NowTicks();
- next_time_to_send_rtcp_ = now + time_to_next;
+ next_time_to_send_rtcp_ = now + rtcp_interval_;
}
void Rtcp::OnReceivedReceiverLog(const RtcpReceiverLogMessage& receiver_log) {
diff --git a/media/cast/test/cast_benchmarks.cc b/media/cast/test/cast_benchmarks.cc
index f074417..a9242b4 100644
--- a/media/cast/test/cast_benchmarks.cc
+++ b/media/cast/test/cast_benchmarks.cc
@@ -46,6 +46,7 @@
#include "media/cast/cast_sender.h"
#include "media/cast/logging/simple_event_subscriber.h"
#include "media/cast/test/fake_single_thread_task_runner.h"
+#include "media/cast/test/loopback_transport.h"
#include "media/cast/test/skewed_single_thread_task_runner.h"
#include "media/cast/test/skewed_tick_clock.h"
#include "media/cast/test/utility/audio_utility.h"
@@ -93,65 +94,6 @@ void IgnoreRawEvents(const std::vector<PacketEvent>& packet_events) {
} // namespace
-// Shim that turns forwards packets from a test::PacketPipe to a
-// PacketReceiverCallback.
-class LoopBackPacketPipe : public test::PacketPipe {
- public:
- LoopBackPacketPipe(const transport::PacketReceiverCallback& packet_receiver)
- : packet_receiver_(packet_receiver) {}
-
- virtual ~LoopBackPacketPipe() {}
-
- // PacketPipe implementations.
- virtual void Send(scoped_ptr<transport::Packet> packet) OVERRIDE {
- packet_receiver_.Run(packet.Pass());
- }
-
- private:
- transport::PacketReceiverCallback packet_receiver_;
-};
-
-// Class that sends the packet direct from sender into the receiver with the
-// ability to drop packets between the two.
-// TODO(hubbe): Break this out and share code with end2end_unittest.cc
-class LoopBackTransport : public transport::PacketSender {
- public:
- explicit LoopBackTransport(scoped_refptr<CastEnvironment> cast_environment)
- : cast_environment_(cast_environment) {}
-
- void SetPacketReceiver(
- const transport::PacketReceiverCallback& packet_receiver,
- const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
- base::TickClock* clock) {
- scoped_ptr<test::PacketPipe> loopback_pipe(
- new LoopBackPacketPipe(packet_receiver));
- if (packet_pipe_) {
- packet_pipe_->AppendToPipe(loopback_pipe.Pass());
- } else {
- packet_pipe_ = loopback_pipe.Pass();
- }
- packet_pipe_->InitOnIOThread(task_runner, clock);
- }
-
- virtual bool SendPacket(transport::PacketRef packet,
- const base::Closure& cb) OVERRIDE {
- DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
- scoped_ptr<Packet> packet_copy(new Packet(packet->data));
- packet_pipe_->Send(packet_copy.Pass());
- return true;
- }
-
- void SetPacketPipe(scoped_ptr<test::PacketPipe> pipe) {
- // Append the loopback pipe to the end.
- pipe->AppendToPipe(packet_pipe_.Pass());
- packet_pipe_ = pipe.Pass();
- }
-
- private:
- scoped_refptr<CastEnvironment> cast_environment_;
- scoped_ptr<test::PacketPipe> packet_pipe_;
-};
-
// Wraps a CastTransportSender and records some statistics about
// the data that goes through it.
class CastTransportSenderWrapper : public transport::CastTransportSender {
@@ -350,7 +292,7 @@ class RunOneBenchmark {
task_runner_receiver_->SetSkew(1.0 / skew);
}
- void Create() {
+ void Create(const MeasuringPoint& p) {
cast_receiver_ = CastReceiver::Create(cast_environment_receiver_,
audio_receiver_config_,
video_receiver_config_,
@@ -379,10 +321,12 @@ class RunOneBenchmark {
CreateDefaultVideoEncodeAcceleratorCallback(),
CreateDefaultVideoEncodeMemoryCallback());
- receiver_to_sender_.SetPacketReceiver(
- cast_sender_->packet_receiver(), task_runner_, &testing_clock_);
- sender_to_receiver_.SetPacketReceiver(
- cast_receiver_->packet_receiver(), task_runner_, &testing_clock_);
+ receiver_to_sender_.Initialize(
+ CreateSimplePipe(p).Pass(), cast_sender_->packet_receiver(),
+ task_runner_, &testing_clock_);
+ sender_to_receiver_.Initialize(
+ CreateSimplePipe(p).Pass(), cast_receiver_->packet_receiver(),
+ task_runner_, &testing_clock_);
}
virtual ~RunOneBenchmark() {
@@ -440,9 +384,7 @@ class RunOneBenchmark {
available_bitrate_ = p.bitrate;
Configure(
transport::CODEC_VIDEO_FAKE, transport::CODEC_AUDIO_PCM16, 32000, 1);
- receiver_to_sender_.SetPacketPipe(CreateSimplePipe(p).Pass());
- sender_to_receiver_.SetPacketPipe(CreateSimplePipe(p).Pass());
- Create();
+ Create(p);
StartBasicPlayer();
for (int frame = 0; frame < 1000; frame++) {
diff --git a/media/cast/test/fake_media_source.cc b/media/cast/test/fake_media_source.cc
index 994feb5..07baebe 100644
--- a/media/cast/test/fake_media_source.cc
+++ b/media/cast/test/fake_media_source.cc
@@ -240,7 +240,7 @@ void FakeMediaSource::SendNextFakeFrame() {
if (start_time_.is_null())
start_time_ = now;
- base::TimeDelta video_time = VideoFrameTime(video_frame_count_);
+ base::TimeDelta video_time = VideoFrameTime(++video_frame_count_);
video_frame->set_timestamp(video_time);
video_frame_input_->InsertRawVideoFrame(video_frame,
start_time_ + video_time);
diff --git a/media/cast/test/loopback_transport.cc b/media/cast/test/loopback_transport.cc
new file mode 100644
index 0000000..3914e33
--- /dev/null
+++ b/media/cast/test/loopback_transport.cc
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/cast/test/loopback_transport.h"
+
+#include "base/single_thread_task_runner.h"
+#include "base/time/tick_clock.h"
+#include "media/cast/test/utility/udp_proxy.h"
+
+namespace media {
+namespace cast {
+namespace {
+
+// Shim that turns forwards packets from a test::PacketPipe to a
+// PacketReceiverCallback.
+class LoopBackPacketPipe : public test::PacketPipe {
+ public:
+ LoopBackPacketPipe(
+ const transport::PacketReceiverCallback& packet_receiver)
+ : packet_receiver_(packet_receiver) {}
+
+ virtual ~LoopBackPacketPipe() {}
+
+ // PacketPipe implementations.
+ virtual void Send(scoped_ptr<transport::Packet> packet) OVERRIDE {
+ packet_receiver_.Run(packet.Pass());
+ }
+
+ private:
+ transport::PacketReceiverCallback packet_receiver_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoopBackPacketPipe);
+};
+
+} // namespace
+
+LoopBackTransport::LoopBackTransport(
+ scoped_refptr<CastEnvironment> cast_environment)
+ : cast_environment_(cast_environment) {
+}
+
+LoopBackTransport::~LoopBackTransport() {
+}
+
+bool LoopBackTransport::SendPacket(transport::PacketRef packet,
+ const base::Closure& cb) {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ scoped_ptr<Packet> packet_copy(new Packet(packet->data));
+ packet_pipe_->Send(packet_copy.Pass());
+ return true;
+}
+
+void LoopBackTransport::Initialize(
+ scoped_ptr<test::PacketPipe> pipe,
+ const transport::PacketReceiverCallback& packet_receiver,
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ base::TickClock* clock) {
+ scoped_ptr<test::PacketPipe> loopback_pipe(
+ new LoopBackPacketPipe(packet_receiver));
+ // Append the loopback pipe to the end.
+ pipe->AppendToPipe(loopback_pipe.Pass());
+ packet_pipe_ = pipe.Pass();
+ packet_pipe_->InitOnIOThread(task_runner, clock);
+}
+
+} // namespace cast
+} // namespace media
diff --git a/media/cast/test/loopback_transport.h b/media/cast/test/loopback_transport.h
new file mode 100644
index 0000000..3c5978c
--- /dev/null
+++ b/media/cast/test/loopback_transport.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_CAST_TEST_LOOPBACK_TRANSPORT_H_
+#define MEDIA_CAST_TEST_LOOPBACK_TRANSPORT_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/cast/cast_environment.h"
+#include "media/cast/transport/cast_transport_config.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+class TickClock;
+} // namespace base
+
+namespace media {
+namespace cast {
+
+namespace test {
+class PacketPipe;
+} // namespace test
+
+// Class that sends the packet to a receiver through a stack of PacketPipes.
+class LoopBackTransport : public transport::PacketSender {
+ public:
+ explicit LoopBackTransport(
+ scoped_refptr<CastEnvironment> cast_environment);
+ virtual ~LoopBackTransport();
+
+ virtual bool SendPacket(transport::PacketRef packet,
+ const base::Closure& cb) OVERRIDE;
+
+ // Initiailize this loopback transport.
+ // Establish a flow of packets from |pipe| to |packet_receiver|.
+ // The data flow looks like:
+ // SendPacket() -> |pipe| -> Fake loopback pipe -> |packet_receiver|.
+ void Initialize(
+ scoped_ptr<test::PacketPipe> pipe,
+ const transport::PacketReceiverCallback& packet_receiver,
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ base::TickClock* clock);
+
+ private:
+ const scoped_refptr<CastEnvironment> cast_environment_;
+ scoped_ptr<test::PacketPipe> packet_pipe_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoopBackTransport);
+};
+
+} // namespace cast
+} // namespace media
+
+#endif // MEDIA_CAST_TEST_LOOPBACK_TRANSPORT_H_
diff --git a/media/cast/test/simulator.cc b/media/cast/test/simulator.cc
index f43ec0a..b96f479 100644
--- a/media/cast/test/simulator.cc
+++ b/media/cast/test/simulator.cc
@@ -2,40 +2,365 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Simulation program.
+// Simulate end to end streaming.
+//
// Input:
-// - File path to writing out the raw event log of the simulation session.
-// - Simulation parameters.
-// - Unique simulation run ID for tagging the log.
+// --source=
+// WebM used as the source of video and audio frames.
+// --output=
+// File path to writing out the raw event log of the simulation session.
+// --sim-id=
+// Unique simulation ID.
+//
// Output:
// - Raw event log of the simulation session tagged with the unique test ID,
// written out to the specified file path.
#include "base/at_exit.h"
+#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"
#include "base/files/scoped_file.h"
+#include "base/json/json_writer.h"
#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/time/tick_clock.h"
+#include "base/values.h"
+#include "media/base/audio_bus.h"
+#include "media/base/media.h"
+#include "media/base/video_frame.h"
+#include "media/cast/cast_config.h"
+#include "media/cast/cast_environment.h"
+#include "media/cast/cast_receiver.h"
+#include "media/cast/cast_sender.h"
+#include "media/cast/logging/encoding_event_subscriber.h"
+#include "media/cast/logging/log_serializer.h"
+#include "media/cast/logging/logging_defines.h"
+#include "media/cast/logging/proto/raw_events.pb.h"
+#include "media/cast/logging/raw_event_subscriber_bundle.h"
+#include "media/cast/logging/simple_event_subscriber.h"
+#include "media/cast/test/fake_media_source.h"
+#include "media/cast/test/fake_single_thread_task_runner.h"
+#include "media/cast/test/loopback_transport.h"
+#include "media/cast/test/skewed_tick_clock.h"
+#include "media/cast/test/utility/audio_utility.h"
+#include "media/cast/test/utility/default_config.h"
+#include "media/cast/test/utility/test_util.h"
+#include "media/cast/test/utility/udp_proxy.h"
+#include "media/cast/test/utility/video_utility.h"
+#include "media/cast/transport/cast_transport_config.h"
+#include "media/cast/transport/cast_transport_defines.h"
+#include "media/cast/transport/cast_transport_sender.h"
+#include "media/cast/transport/cast_transport_sender_impl.h"
-const char kSimulationRunId[] = "simulation-run-id";
-const char kOutputPath[] = "output-path";
+namespace media {
+namespace cast {
+namespace {
+
+const int kTargetDelay = 300;
+const char kSourcePath[] = "source";
+const char kOutputPath[] = "output";
+const char kSimulationId[] = "sim-id";
+
+void UpdateCastTransportStatus(transport::CastTransportStatus status) {
+ LOG(INFO) << "Cast transport status: " << status;
+}
+
+void AudioInitializationStatus(CastInitializationStatus status) {
+ LOG(INFO) << "Audio status: " << status;
+}
+
+void VideoInitializationStatus(CastInitializationStatus status) {
+ LOG(INFO) << "Video status: " << status;
+}
+
+void LogTransportEvents(const scoped_refptr<CastEnvironment>& env,
+ const std::vector<PacketEvent>& packet_events) {
+ for (std::vector<media::cast::PacketEvent>::const_iterator it =
+ packet_events.begin();
+ it != packet_events.end();
+ ++it) {
+ env->Logging()->InsertPacketEvent(it->timestamp,
+ it->type,
+ it->media_type,
+ it->rtp_timestamp,
+ it->frame_id,
+ it->packet_id,
+ it->max_packet_id,
+ it->size);
+ }
+}
+
+void GotVideoFrame(
+ int* counter,
+ CastReceiver* cast_receiver,
+ const scoped_refptr<media::VideoFrame>& video_frame,
+ const base::TimeTicks& render_time,
+ bool continuous) {
+ ++*counter;
+ cast_receiver->RequestDecodedVideoFrame(
+ base::Bind(&GotVideoFrame, counter, cast_receiver));
+}
+
+void GotAudioFrame(
+ int* counter,
+ CastReceiver* cast_receiver,
+ scoped_ptr<AudioBus> audio_bus,
+ const base::TimeTicks& playout_time,
+ bool is_continuous) {
+ ++*counter;
+ cast_receiver->RequestDecodedAudioFrame(
+ base::Bind(&GotAudioFrame, counter, cast_receiver));
+}
+
+void AppendLog(EncodingEventSubscriber* subscriber,
+ const std::string& extra_data,
+ const base::FilePath& output_path) {
+ media::cast::proto::LogMetadata metadata;
+ metadata.set_extra_data(extra_data);
+
+ media::cast::FrameEventList frame_events;
+ media::cast::PacketEventList packet_events;
+ subscriber->GetEventsAndReset(
+ &metadata, &frame_events, &packet_events);
+ media::cast::proto::GeneralDescription* gen_desc =
+ metadata.mutable_general_description();
+ gen_desc->set_product("Cast Simulator");
+ gen_desc->set_product_version("0.1");
+
+ scoped_ptr<char[]> serialized_log(new char[media::cast::kMaxSerializedBytes]);
+ int output_bytes;
+ bool success = media::cast::SerializeEvents(metadata,
+ frame_events,
+ packet_events,
+ true,
+ media::cast::kMaxSerializedBytes,
+ serialized_log.get(),
+ &output_bytes);
+
+ if (!success) {
+ LOG(ERROR) << "Failed to serialize log.";
+ return;
+ }
+
+ if (AppendToFile(output_path, serialized_log.get(), output_bytes) == -1) {
+ LOG(ERROR) << "Failed to append to log.";
+ }
+}
+
+// Run simulation once.
+//
+// |output_path| is the path to write serialized log.
+// |extra_data| is extra tagging information to write to log.
+void RunSimulation(const base::FilePath& source_path,
+ const base::FilePath& output_path,
+ const std::string& extra_data) {
+ // Fake clock. Make sure start time is non zero.
+ base::SimpleTestTickClock testing_clock;
+ testing_clock.Advance(base::TimeDelta::FromSeconds(1));
+
+ // Task runner.
+ scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner =
+ new test::FakeSingleThreadTaskRunner(&testing_clock);
+ base::ThreadTaskRunnerHandle task_runner_handle(task_runner);
+
+ // CastEnvironments.
+ scoped_refptr<CastEnvironment> sender_env =
+ new CastEnvironment(
+ scoped_ptr<base::TickClock>(
+ new test::SkewedTickClock(&testing_clock)).Pass(),
+ task_runner,
+ task_runner,
+ task_runner);
+ scoped_refptr<CastEnvironment> receiver_env =
+ new CastEnvironment(
+ scoped_ptr<base::TickClock>(
+ new test::SkewedTickClock(&testing_clock)).Pass(),
+ task_runner,
+ task_runner,
+ task_runner);
+
+ // Event subscriber. Store at most 1 hour of events.
+ EncodingEventSubscriber audio_event_subscriber(AUDIO_EVENT,
+ 100 * 60 * 60);
+ EncodingEventSubscriber video_event_subscriber(VIDEO_EVENT,
+ 30 * 60 * 60);
+ sender_env->Logging()->AddRawEventSubscriber(&audio_event_subscriber);
+ sender_env->Logging()->AddRawEventSubscriber(&video_event_subscriber);
+
+ // Audio sender config.
+ AudioSenderConfig audio_sender_config = GetDefaultAudioSenderConfig();
+ audio_sender_config.target_playout_delay =
+ base::TimeDelta::FromMilliseconds(kTargetDelay);
+
+ // Audio receiver config.
+ FrameReceiverConfig audio_receiver_config =
+ GetDefaultAudioReceiverConfig();
+ audio_receiver_config.rtp_max_delay_ms =
+ audio_sender_config.target_playout_delay.InMilliseconds();
+
+ // Video sender config.
+ VideoSenderConfig video_sender_config = GetDefaultVideoSenderConfig();
+ video_sender_config.max_bitrate = 4000000;
+ video_sender_config.min_bitrate = 2000000;
+ video_sender_config.start_bitrate = 4000000;
+ video_sender_config.target_playout_delay =
+ base::TimeDelta::FromMilliseconds(kTargetDelay);
+
+ // Video receiver config.
+ FrameReceiverConfig video_receiver_config =
+ GetDefaultVideoReceiverConfig();
+ video_receiver_config.rtp_max_delay_ms =
+ video_sender_config.target_playout_delay.InMilliseconds();
+
+ // Loopback transport.
+ LoopBackTransport receiver_to_sender(receiver_env);
+ LoopBackTransport sender_to_receiver(sender_env);
+
+ // Cast receiver.
+ scoped_ptr<CastReceiver> cast_receiver(
+ CastReceiver::Create(receiver_env,
+ audio_receiver_config,
+ video_receiver_config,
+ &receiver_to_sender));
+
+ // Cast sender and transport sender.
+ scoped_ptr<transport::CastTransportSender> transport_sender(
+ new transport::CastTransportSenderImpl(
+ NULL,
+ &testing_clock,
+ net::IPEndPoint(),
+ base::Bind(&UpdateCastTransportStatus),
+ base::Bind(&LogTransportEvents, sender_env),
+ base::TimeDelta::FromSeconds(1),
+ task_runner,
+ &sender_to_receiver));
+ scoped_ptr<CastSender> cast_sender(
+ CastSender::Create(sender_env, transport_sender.get()));
+
+ // Build packet pipe.
+ // TODO(hclam): Allow user to input these parameters. Following
+ // parameters are taken from a session from real-world data. It is
+ // chosen here because it gives a difficult environment.
+ std::vector<double> average_rates;
+ average_rates.push_back(0.609);
+ average_rates.push_back(0.495);
+ average_rates.push_back(0.561);
+ average_rates.push_back(0.458);
+ average_rates.push_back(0.538);
+ average_rates.push_back(0.513);
+ average_rates.push_back(0.585);
+ average_rates.push_back(0.592);
+ average_rates.push_back(0.658);
+ average_rates.push_back(0.556);
+ average_rates.push_back(0.371);
+ average_rates.push_back(0.595);
+ average_rates.push_back(0.490);
+ average_rates.push_back(0.980);
+ average_rates.push_back(0.781);
+ average_rates.push_back(0.463);
+ test::InterruptedPoissonProcess ipp(average_rates, 0.3, 4.1, 0);
+
+ // Connect sender to receiver. This initializes the pipe.
+ receiver_to_sender.Initialize(
+ ipp.NewBuffer(128 * 1024), cast_sender->packet_receiver(), task_runner,
+ &testing_clock);
+ sender_to_receiver.Initialize(
+ ipp.NewBuffer(128 * 1024), cast_receiver->packet_receiver(), task_runner,
+ &testing_clock);
+
+ // Start receiver.
+ int audio_frame_count = 0;
+ int video_frame_count = 0;
+ cast_receiver->RequestDecodedVideoFrame(
+ base::Bind(&GotVideoFrame, &video_frame_count, cast_receiver.get()));
+ cast_receiver->RequestDecodedAudioFrame(
+ base::Bind(&GotAudioFrame, &audio_frame_count, cast_receiver.get()));
+
+ FakeMediaSource media_source(task_runner,
+ &testing_clock,
+ video_sender_config);
+
+ // Initializing audio and video senders.
+ cast_sender->InitializeAudio(audio_sender_config,
+ base::Bind(&AudioInitializationStatus));
+ cast_sender->InitializeVideo(media_source.get_video_config(),
+ base::Bind(&VideoInitializationStatus),
+ CreateDefaultVideoEncodeAcceleratorCallback(),
+ CreateDefaultVideoEncodeMemoryCallback());
+
+ // Start sending.
+ if (!source_path.empty()) {
+ // 0 means using the FPS from the file.
+ media_source.SetSourceFile(source_path, 0);
+ }
+ media_source.Start(cast_sender->audio_frame_input(),
+ cast_sender->video_frame_input());
+
+ // Run for 5 minutes.
+ base::TimeDelta elapsed_time;
+ while (elapsed_time.InMinutes() < 5) {
+ // Each step is 100us.
+ base::TimeDelta step = base::TimeDelta::FromMicroseconds(100);
+ task_runner->Sleep(step);
+ elapsed_time += step;
+ }
+
+ LOG(INFO) << "Audio frame count: " << audio_frame_count;
+ LOG(INFO) << "Video frame count: " << video_frame_count;
+ LOG(INFO) << "Writing log: " << output_path.value();
+
+ // Truncate file and then write serialized log.
+ {
+ base::ScopedFILE file(base::OpenFile(output_path, "wb"));
+ if (!file.get()) {
+ LOG(INFO) << "Cannot write to log.";
+ return;
+ }
+ }
+ AppendLog(&video_event_subscriber, extra_data, output_path);
+ AppendLog(&audio_event_subscriber, extra_data, output_path);
+}
+
+} // namespace
+} // namespace cast
+} // namespace media
int main(int argc, char** argv) {
base::AtExitManager at_exit;
CommandLine::Init(argc, argv);
InitLogging(logging::LoggingSettings());
+ base::FilePath media_path;
+ if (!PathService::Get(base::DIR_MODULE, &media_path)) {
+ LOG(ERROR) << "Failed to load FFmpeg.";
+ return 1;
+ }
+ media::InitializeMediaLibrary(media_path);
+
const CommandLine* cmd = CommandLine::ForCurrentProcess();
- base::FilePath output_path = cmd->GetSwitchValuePath(kOutputPath);
- CHECK(!output_path.empty());
- std::string sim_run_id = cmd->GetSwitchValueASCII(kSimulationRunId);
+ base::FilePath source_path = cmd->GetSwitchValuePath(
+ media::cast::kSourcePath);
+ base::FilePath output_path = cmd->GetSwitchValuePath(
+ media::cast::kOutputPath);
+ if (output_path.empty()) {
+ base::GetTempDir(&output_path);
+ output_path = output_path.AppendASCII("sim-events.gz");
+ }
+ std::string sim_id = cmd->GetSwitchValueASCII(media::cast::kSimulationId);
+
+ base::DictionaryValue values;
+ values.SetBoolean("sim", true);
+ values.SetString("sim-id", sim_id);
- std::string msg = "Log from simulation run " + sim_run_id;
- int ret = base::WriteFile(output_path, &msg[0], msg.size());
- if (ret != static_cast<int>(msg.size()))
- VLOG(0) << "Failed to write logs to file.";
+ std::string extra_data;
+ base::JSONWriter::Write(&values, &extra_data);
+ // Run.
+ media::cast::RunSimulation(source_path, output_path, extra_data);
return 0;
}
diff --git a/media/cast/test/utility/default_config.cc b/media/cast/test/utility/default_config.cc
index b6f6065..a0155c4 100644
--- a/media/cast/test/utility/default_config.cc
+++ b/media/cast/test/utility/default_config.cc
@@ -53,6 +53,43 @@ FrameReceiverConfig GetDefaultVideoReceiverConfig() {
return config;
}
+AudioSenderConfig GetDefaultAudioSenderConfig() {
+ FrameReceiverConfig recv_config = GetDefaultAudioReceiverConfig();
+ AudioSenderConfig config;
+ config.ssrc = recv_config.incoming_ssrc;
+ config.incoming_feedback_ssrc = recv_config.feedback_ssrc;
+ config.rtp_payload_type = recv_config.rtp_payload_type;
+ config.use_external_encoder = false;
+ config.frequency = recv_config.frequency;
+ config.channels = recv_config.channels;
+ config.bitrate = kDefaultAudioEncoderBitrate;
+ config.codec = recv_config.codec;
+ config.target_playout_delay =
+ base::TimeDelta::FromMilliseconds(kDefaultRtpMaxDelayMs);
+ return config;
+}
+
+VideoSenderConfig GetDefaultVideoSenderConfig() {
+ FrameReceiverConfig recv_config = GetDefaultVideoReceiverConfig();
+ VideoSenderConfig config;
+ config.ssrc = recv_config.incoming_ssrc;
+ config.incoming_feedback_ssrc = recv_config.feedback_ssrc;
+ config.rtp_payload_type = recv_config.rtp_payload_type;
+ config.use_external_encoder = false;
+ config.width = 1280;
+ config.height = 720;
+ config.max_bitrate = 4000000;
+ config.min_bitrate = 2000000;
+ config.start_bitrate = 4000000;
+ config.max_frame_rate = recv_config.max_frame_rate;
+ config.max_number_of_video_buffers_used = 1;
+ config.codec = recv_config.codec;
+ config.number_of_encode_threads = 2;
+ config.target_playout_delay =
+ base::TimeDelta::FromMilliseconds(kDefaultRtpMaxDelayMs);
+ return config;
+}
+
CreateVideoEncodeAcceleratorCallback
CreateDefaultVideoEncodeAcceleratorCallback() {
return base::Bind(&CreateVideoEncodeAccelerator);
diff --git a/media/cast/test/utility/default_config.h b/media/cast/test/utility/default_config.h
index eaa3c96..2cc52a7 100644
--- a/media/cast/test/utility/default_config.h
+++ b/media/cast/test/utility/default_config.h
@@ -20,6 +20,15 @@ FrameReceiverConfig GetDefaultAudioReceiverConfig();
// name.
FrameReceiverConfig GetDefaultVideoReceiverConfig();
+// Returns a AudioSenderConfig initialized to default values. This means
+// 48 kHz, 2-channel Opus-coded audio. Default values for SSRCs and payload
+// type.
+AudioSenderConfig GetDefaultAudioSenderConfig();
+
+// Returns a VideoSenderConfig initialized to default values. This means
+// 30 Hz VP8 coded code. Default values for SSRCs and payload type.
+VideoSenderConfig GetDefaultVideoSenderConfig();
+
// Returns a callback that does nothing.
CreateVideoEncodeAcceleratorCallback
CreateDefaultVideoEncodeAcceleratorCallback();
diff --git a/media/cast/test/utility/udp_proxy.cc b/media/cast/test/utility/udp_proxy.cc
index 9fc3b4a..7e9c553 100644
--- a/media/cast/test/utility/udp_proxy.cc
+++ b/media/cast/test/utility/udp_proxy.cc
@@ -2,12 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <math.h>
#include <stdlib.h>
+#include <vector>
#include "media/cast/test/utility/udp_proxy.h"
#include "base/logging.h"
-#include "base/memory/linked_ptr.h"
#include "base/rand_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
@@ -310,6 +311,205 @@ scoped_ptr<PacketPipe> NewNetworkGlitchPipe(double average_work_time,
.Pass();
}
+
+// Internal buffer object for a client of the IPP model.
+class InterruptedPoissonProcess::InternalBuffer : public PacketPipe {
+ public:
+ InternalBuffer(base::WeakPtr<InterruptedPoissonProcess> ipp,
+ size_t size)
+ : ipp_(ipp),
+ stored_size_(0),
+ stored_limit_(size),
+ clock_(NULL),
+ weak_factory_(this) {
+ }
+
+ virtual void Send(scoped_ptr<transport::Packet> packet) OVERRIDE {
+ // Drop if buffer is full.
+ if (stored_size_ >= stored_limit_)
+ return;
+ stored_size_ += packet->size();
+ buffer_.push_back(linked_ptr<transport::Packet>(packet.release()));
+ buffer_time_.push_back(clock_->NowTicks());
+ DCHECK(buffer_.size() == buffer_time_.size());
+ }
+
+ virtual void InitOnIOThread(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ base::TickClock* clock) OVERRIDE {
+ clock_ = clock;
+ if (ipp_)
+ ipp_->InitOnIOThread(task_runner, clock);
+ PacketPipe::InitOnIOThread(task_runner, clock);
+ }
+
+ void SendOnePacket() {
+ scoped_ptr<transport::Packet> packet(buffer_.front().release());
+ stored_size_ -= packet->size();
+ buffer_.pop_front();
+ buffer_time_.pop_front();
+ pipe_->Send(packet.Pass());
+ DCHECK(buffer_.size() == buffer_time_.size());
+ }
+
+ bool Empty() const {
+ return buffer_.empty();
+ }
+
+ base::TimeTicks FirstPacketTime() const {
+ DCHECK(!buffer_time_.empty());
+ return buffer_time_.front();
+ }
+
+ base::WeakPtr<InternalBuffer> GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+
+ }
+
+ private:
+ const base::WeakPtr<InterruptedPoissonProcess> ipp_;
+ size_t stored_size_;
+ const size_t stored_limit_;
+ std::deque<linked_ptr<transport::Packet> > buffer_;
+ std::deque<base::TimeTicks> buffer_time_;
+ base::TickClock* clock_;
+ base::WeakPtrFactory<InternalBuffer> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(InternalBuffer);
+};
+
+InterruptedPoissonProcess::InterruptedPoissonProcess(
+ const std::vector<double>& average_rates,
+ double coef_burstiness,
+ double coef_variance,
+ uint32 rand_seed)
+ : clock_(NULL),
+ average_rates_(average_rates),
+ coef_burstiness_(coef_burstiness),
+ coef_variance_(coef_variance),
+ rate_index_(0),
+ on_state_(true),
+ weak_factory_(this) {
+ mt_rand_.init_genrand(rand_seed);
+ DCHECK(!average_rates.empty());
+ ComputeRates();
+}
+
+InterruptedPoissonProcess::~InterruptedPoissonProcess() {
+}
+
+void InterruptedPoissonProcess::InitOnIOThread(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ base::TickClock* clock) {
+ // Already initialized and started.
+ if (task_runner_ && clock_)
+ return;
+ task_runner_ = task_runner;
+ clock_ = clock;
+ UpdateRates();
+ SwitchOn();
+ SendPacket();
+}
+
+scoped_ptr<PacketPipe> InterruptedPoissonProcess::NewBuffer(size_t size) {
+ scoped_ptr<InternalBuffer> buffer(
+ new InternalBuffer(weak_factory_.GetWeakPtr(), size));
+ send_buffers_.push_back(buffer->GetWeakPtr());
+ return buffer.PassAs<PacketPipe>();
+}
+
+base::TimeDelta InterruptedPoissonProcess::NextEvent(double rate) {
+ // Rate is per milliseconds.
+ // The time until next event is exponentially distributed to the
+ // inverse of |rate|.
+ return base::TimeDelta::FromMillisecondsD(
+ fabs(-log(1.0 - RandDouble()) / rate));
+}
+
+double InterruptedPoissonProcess::RandDouble() {
+ // Generate a 64-bits random number from MT19937 and then convert
+ // it to double.
+ uint64 rand = mt_rand_.genrand_int32();
+ rand <<= 32;
+ rand |= mt_rand_.genrand_int32();
+ return base::BitsToOpenEndedUnitInterval(rand);
+}
+
+void InterruptedPoissonProcess::ComputeRates() {
+ double avg_rate = average_rates_[rate_index_];
+
+ send_rate_ = avg_rate / coef_burstiness_;
+ switch_off_rate_ =
+ 2 * avg_rate * (1 - coef_burstiness_) * (1 - coef_burstiness_) /
+ coef_burstiness_ / (coef_variance_ - 1);
+ switch_on_rate_ =
+ 2 * avg_rate * (1 - coef_burstiness_) / (coef_variance_ - 1);
+}
+
+void InterruptedPoissonProcess::UpdateRates() {
+ ComputeRates();
+
+ // Rates are updated once per second.
+ rate_index_ = (rate_index_ + 1) % average_rates_.size();
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&InterruptedPoissonProcess::UpdateRates,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromSeconds(1));
+}
+
+void InterruptedPoissonProcess::SwitchOff() {
+ on_state_ = false;
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&InterruptedPoissonProcess::SwitchOn,
+ weak_factory_.GetWeakPtr()),
+ NextEvent(switch_on_rate_));
+}
+
+void InterruptedPoissonProcess::SwitchOn() {
+ on_state_ = true;
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&InterruptedPoissonProcess::SwitchOff,
+ weak_factory_.GetWeakPtr()),
+ NextEvent(switch_off_rate_));
+}
+
+void InterruptedPoissonProcess::SendPacket() {
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&InterruptedPoissonProcess::SendPacket,
+ weak_factory_.GetWeakPtr()),
+ NextEvent(send_rate_));
+
+ // If OFF then don't send.
+ if (!on_state_)
+ return;
+
+ // Find the earliest packet to send.
+ base::TimeTicks earliest_time;
+ for (size_t i = 0; i < send_buffers_.size(); ++i) {
+ if (!send_buffers_[i])
+ continue;
+ if (send_buffers_[i]->Empty())
+ continue;
+ if (earliest_time.is_null() ||
+ send_buffers_[i]->FirstPacketTime() < earliest_time)
+ earliest_time = send_buffers_[i]->FirstPacketTime();
+ }
+ for (size_t i = 0; i < send_buffers_.size(); ++i) {
+ if (!send_buffers_[i])
+ continue;
+ if (send_buffers_[i]->Empty())
+ continue;
+ if (send_buffers_[i]->FirstPacketTime() != earliest_time)
+ continue;
+ send_buffers_[i]->SendOnePacket();
+ break;
+ }
+}
+
class UDPProxyImpl;
class PacketSender : public PacketPipe {
diff --git a/media/cast/test/utility/udp_proxy.h b/media/cast/test/utility/udp_proxy.h
index b102573..3321e04 100644
--- a/media/cast/test/utility/udp_proxy.h
+++ b/media/cast/test/utility/udp_proxy.h
@@ -8,11 +8,14 @@
#include <vector>
#include "base/basictypes.h"
+#include "base/memory/linked_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "media/cast/transport/cast_transport_config.h"
#include "net/base/ip_endpoint.h"
+#include "third_party/mt19937ar/mt19937ar.h"
namespace net {
class NetLog;
@@ -43,6 +46,63 @@ class PacketPipe {
base::TickClock* clock_;
};
+// Implements a Interrupted Poisson Process for packet delivery.
+// The process has 2 states: ON and OFF, the rate of switching between
+// these two states are defined.
+// When in ON state packets are sent according to a defined rate.
+// When in OFF state packets are not sent.
+// The rate above is the average rate of a poisson distribution.
+class InterruptedPoissonProcess {
+ public:
+ InterruptedPoissonProcess(
+ const std::vector<double>& average_rates,
+ double coef_burstiness,
+ double coef_variance,
+ uint32 rand_seed);
+ ~InterruptedPoissonProcess();
+
+ scoped_ptr<PacketPipe> NewBuffer(size_t size);
+
+ private:
+ class InternalBuffer;
+
+ // |task_runner| is the executor of the IO thread.
+ // |clock| is the system clock.
+ void InitOnIOThread(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ base::TickClock* clock);
+
+ base::TimeDelta NextEvent(double rate);
+ double RandDouble();
+ void ComputeRates();
+ void UpdateRates();
+ void SwitchOff();
+ void SwitchOn();
+ void SendPacket();
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ base::TickClock* clock_;
+ const std::vector<double> average_rates_;
+ const double coef_burstiness_;
+ const double coef_variance_;
+ int rate_index_;
+
+ // The following rates are per milliseconds.
+ double send_rate_;
+ double switch_off_rate_;
+ double switch_on_rate_;
+ bool on_state_;
+
+ std::vector<base::WeakPtr<InternalBuffer> > send_buffers_;
+
+ // Fast pseudo random number generator.
+ MersenneTwister mt_rand_;
+
+ base::WeakPtrFactory<InterruptedPoissonProcess> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterruptedPoissonProcess);
+};
+
// A UDPProxy will set up a UDP socket and bind to |local_port|.
// Packets send to that port will be forwarded to |destination|.
// Packets send from |destination| to |local_port| will be returned
diff --git a/media/cast/video_sender/fake_software_video_encoder.cc b/media/cast/video_sender/fake_software_video_encoder.cc
index 7c5c952..e7bd950 100644
--- a/media/cast/video_sender/fake_software_video_encoder.cc
+++ b/media/cast/video_sender/fake_software_video_encoder.cc
@@ -47,7 +47,7 @@ bool FakeSoftwareVideoEncoder::Encode(
values.SetInteger("size", frame_size_);
base::JSONWriter::Write(&values, &encoded_image->data);
encoded_image->data.resize(
- std::max<size_t>(encoded_image->data.size(), frame_size_));
+ std::max<size_t>(encoded_image->data.size(), frame_size_), ' ');
return true;
}