summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
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;
}