Index: media/cast/test/simulator.cc |
diff --git a/media/cast/test/simulator.cc b/media/cast/test/simulator.cc |
index f43ec0ac9ef7e46f2679ded965899bb4fbe5f7d8..b6363e9edb0d58430ba7242bf1cd1c6eb20eca6b 100644 |
--- a/media/cast/test/simulator.cc |
+++ b/media/cast/test/simulator.cc |
@@ -2,25 +2,352 @@ |
// 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/proto/network_simulation_model.pb.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"; |
+using media::cast::proto::IPPModel; |
+using media::cast::proto::NetworkSimulationModel; |
+using media::cast::proto::NetworkSimulationModelType; |
+ |
+namespace media { |
+namespace cast { |
+namespace { |
+const int kTargetDelay = 300; |
+const char kSourcePath[] = "source"; |
+const char kModelPath[] = "model-path"; |
Alpha Left Google
2014/07/01 21:50:37
nit: Just be "model".
imcheng
2014/07/02 00:10:08
Done.
|
+const char kOutputPath[] = "output"; |
+const char kSimulationId[] = "sim-id"; |
+const char kFfmpegLibDir[] = "ffmpeg-lib-dir"; |
Alpha Left Google
2014/07/01 21:50:38
nit: Just be "lib".
imcheng
2014/07/02 00:10:08
Changed to lib-dir since it needs to be directory.
|
+ |
+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, |
+ const NetworkSimulationModel& model) { |
+ // 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. |
+ if (model.type() != media::cast::proto::INTERRUPTED_POISSON_PROCESS) { |
+ LOG(ERROR) << "Unknown model type " << model.type() << "."; |
Alpha Left Google
2014/07/01 21:50:37
If not model just use a default of say:
avg = 0.60
imcheng
2014/07/02 00:10:08
Done. Using previous model.
|
+ return; |
+ } |
+ |
+ const IPPModel& ipp_model = model.ipp(); |
+ |
+ std::vector<double> average_rates(ipp_model.average_rate_size()); |
+ std::copy(ipp_model.average_rate().begin(), ipp_model.average_rate().end(), |
+ average_rates.begin()); |
+ test::InterruptedPoissonProcess ipp(average_rates, |
+ ipp_model.coef_burstiness(), ipp_model.coef_variance(), 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 3 minutes. |
+ base::TimeDelta elapsed_time; |
+ while (elapsed_time.InMinutes() < 3) { |
+ // 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); |
+} |
+ |
+bool IsModelValid(const NetworkSimulationModel& model) { |
+ if (!model.has_type()) |
+ return false; |
+ NetworkSimulationModelType type = model.type(); |
+ if (type == media::cast::proto::INTERRUPTED_POISSON_PROCESS) { |
+ if (!model.has_ipp()) |
+ return false; |
+ const IPPModel& ipp = model.ipp(); |
+ if (ipp.coef_burstiness() <= 0.0 || ipp.coef_variance() <= 0.0) |
+ return false; |
+ if (ipp.average_rate_size() == 0) |
+ return false; |
+ for (int i = 0; i < ipp.average_rate_size(); i++) { |
+ if (ipp.average_rate(i) <= 0.0) |
+ return false; |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+} // namespace |
+} // namespace cast |
+} // namespace media |
int main(int argc, char** argv) { |
base::AtExitManager at_exit; |
@@ -28,14 +355,59 @@ int main(int argc, char** argv) { |
InitLogging(logging::LoggingSettings()); |
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 media_path = |
+ cmd->GetSwitchValuePath(media::cast::kFfmpegLibDir); |
+ if (media_path.empty()) { |
+ if (!PathService::Get(base::DIR_MODULE, &media_path)) { |
+ LOG(ERROR) << "Failed to load FFmpeg."; |
+ return 1; |
+ } |
+ } |
+ |
+ if (!media::InitializeMediaLibrary(media_path)) { |
+ LOG(ERROR) << "Failed to initialize FFmpeg."; |
+ return 1; |
+ } |
+ |
+ 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::FilePath model_path = cmd->GetSwitchValuePath(media::cast::kModelPath); |
+ if (model_path.empty()) { |
+ LOG(ERROR) << "Model path not set."; |
+ return 1; |
+ } |
+ std::string model_str; |
+ if (!base::ReadFileToString(model_path, &model_str)) { |
+ LOG(ERROR) << "Failed to read model file."; |
+ return 1; |
+ } |
+ |
+ NetworkSimulationModel model; |
+ if (!model.ParseFromString(model_str)) { |
+ LOG(ERROR) << "Failed to parse model."; |
+ return 1; |
+ } |
+ if (!media::cast::IsModelValid(model)) { |
+ LOG(ERROR) << "Invalid model."; |
+ return 1; |
+ } |
+ |
+ 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, model); |
return 0; |
} |