diff options
Diffstat (limited to 'media')
-rw-r--r-- | media/cast/DEPS | 1 | ||||
-rw-r--r-- | media/cast/cast_testing.gypi | 4 | ||||
-rw-r--r-- | media/cast/receiver/video_decoder.cc | 7 | ||||
-rw-r--r-- | media/cast/rtcp/rtcp.cc | 7 | ||||
-rw-r--r-- | media/cast/test/cast_benchmarks.cc | 76 | ||||
-rw-r--r-- | media/cast/test/fake_media_source.cc | 2 | ||||
-rw-r--r-- | media/cast/test/loopback_transport.cc | 68 | ||||
-rw-r--r-- | media/cast/test/loopback_transport.h | 55 | ||||
-rw-r--r-- | media/cast/test/simulator.cc | 351 | ||||
-rw-r--r-- | media/cast/test/utility/default_config.cc | 37 | ||||
-rw-r--r-- | media/cast/test/utility/default_config.h | 9 | ||||
-rw-r--r-- | media/cast/test/utility/udp_proxy.cc | 202 | ||||
-rw-r--r-- | media/cast/test/utility/udp_proxy.h | 60 | ||||
-rw-r--r-- | media/cast/video_sender/fake_software_video_encoder.cc | 2 |
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; } |