Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // Simulation program. | 5 // Simulate end to end streaming. |
| 6 // | |
| 6 // Input: | 7 // Input: |
| 7 // - File path to writing out the raw event log of the simulation session. | 8 // --source= |
| 8 // - Simulation parameters. | 9 // WebM used as the source of video and audio frames. |
| 9 // - Unique simulation run ID for tagging the log. | 10 // --output= |
| 11 // File path to writing out the raw event log of the simulation session. | |
| 12 // --sim-id= | |
| 13 // Unique simulation ID. | |
| 14 // | |
| 10 // Output: | 15 // Output: |
| 11 // - Raw event log of the simulation session tagged with the unique test ID, | 16 // - Raw event log of the simulation session tagged with the unique test ID, |
| 12 // written out to the specified file path. | 17 // written out to the specified file path. |
| 13 | 18 |
| 14 #include "base/at_exit.h" | 19 #include "base/at_exit.h" |
| 20 #include "base/base_paths.h" | |
| 15 #include "base/command_line.h" | 21 #include "base/command_line.h" |
| 16 #include "base/file_util.h" | 22 #include "base/file_util.h" |
| 17 #include "base/files/file_path.h" | 23 #include "base/files/file_path.h" |
| 18 #include "base/files/memory_mapped_file.h" | 24 #include "base/files/memory_mapped_file.h" |
| 19 #include "base/files/scoped_file.h" | 25 #include "base/files/scoped_file.h" |
| 26 #include "base/json/json_writer.h" | |
| 20 #include "base/logging.h" | 27 #include "base/logging.h" |
| 21 | 28 #include "base/path_service.h" |
| 22 const char kSimulationRunId[] = "simulation-run-id"; | 29 #include "base/test/simple_test_tick_clock.h" |
| 23 const char kOutputPath[] = "output-path"; | 30 #include "base/thread_task_runner_handle.h" |
| 31 #include "base/time/tick_clock.h" | |
| 32 #include "base/values.h" | |
| 33 #include "media/base/audio_bus.h" | |
| 34 #include "media/base/media.h" | |
| 35 #include "media/base/video_frame.h" | |
| 36 #include "media/cast/cast_config.h" | |
| 37 #include "media/cast/cast_environment.h" | |
| 38 #include "media/cast/cast_receiver.h" | |
| 39 #include "media/cast/cast_sender.h" | |
| 40 #include "media/cast/logging/encoding_event_subscriber.h" | |
| 41 #include "media/cast/logging/log_serializer.h" | |
| 42 #include "media/cast/logging/logging_defines.h" | |
| 43 #include "media/cast/logging/proto/raw_events.pb.h" | |
| 44 #include "media/cast/logging/raw_event_subscriber_bundle.h" | |
| 45 #include "media/cast/logging/simple_event_subscriber.h" | |
| 46 #include "media/cast/test/fake_media_source.h" | |
| 47 #include "media/cast/test/fake_single_thread_task_runner.h" | |
| 48 #include "media/cast/test/loopback_transport.h" | |
| 49 #include "media/cast/test/proto/network_simulation_model.pb.h" | |
| 50 #include "media/cast/test/skewed_tick_clock.h" | |
| 51 #include "media/cast/test/utility/audio_utility.h" | |
| 52 #include "media/cast/test/utility/default_config.h" | |
| 53 #include "media/cast/test/utility/test_util.h" | |
| 54 #include "media/cast/test/utility/udp_proxy.h" | |
| 55 #include "media/cast/test/utility/video_utility.h" | |
| 56 #include "media/cast/transport/cast_transport_config.h" | |
| 57 #include "media/cast/transport/cast_transport_defines.h" | |
| 58 #include "media/cast/transport/cast_transport_sender.h" | |
| 59 #include "media/cast/transport/cast_transport_sender_impl.h" | |
| 60 | |
| 61 using media::cast::proto::IPPModel; | |
| 62 using media::cast::proto::NetworkSimulationModel; | |
| 63 using media::cast::proto::NetworkSimulationModelType; | |
| 64 | |
| 65 namespace media { | |
| 66 namespace cast { | |
| 67 namespace { | |
| 68 const int kTargetDelay = 300; | |
| 69 const char kSourcePath[] = "source"; | |
| 70 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.
| |
| 71 const char kOutputPath[] = "output"; | |
| 72 const char kSimulationId[] = "sim-id"; | |
| 73 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.
| |
| 74 | |
| 75 void UpdateCastTransportStatus(transport::CastTransportStatus status) { | |
| 76 LOG(INFO) << "Cast transport status: " << status; | |
| 77 } | |
| 78 | |
| 79 void AudioInitializationStatus(CastInitializationStatus status) { | |
| 80 LOG(INFO) << "Audio status: " << status; | |
| 81 } | |
| 82 | |
| 83 void VideoInitializationStatus(CastInitializationStatus status) { | |
| 84 LOG(INFO) << "Video status: " << status; | |
| 85 } | |
| 86 | |
| 87 void LogTransportEvents(const scoped_refptr<CastEnvironment>& env, | |
| 88 const std::vector<PacketEvent>& packet_events) { | |
| 89 for (std::vector<media::cast::PacketEvent>::const_iterator it = | |
| 90 packet_events.begin(); | |
| 91 it != packet_events.end(); | |
| 92 ++it) { | |
| 93 env->Logging()->InsertPacketEvent(it->timestamp, | |
| 94 it->type, | |
| 95 it->media_type, | |
| 96 it->rtp_timestamp, | |
| 97 it->frame_id, | |
| 98 it->packet_id, | |
| 99 it->max_packet_id, | |
| 100 it->size); | |
| 101 } | |
| 102 } | |
| 103 | |
| 104 void GotVideoFrame( | |
| 105 int* counter, | |
| 106 CastReceiver* cast_receiver, | |
| 107 const scoped_refptr<media::VideoFrame>& video_frame, | |
| 108 const base::TimeTicks& render_time, | |
| 109 bool continuous) { | |
| 110 ++*counter; | |
| 111 cast_receiver->RequestDecodedVideoFrame( | |
| 112 base::Bind(&GotVideoFrame, counter, cast_receiver)); | |
| 113 } | |
| 114 | |
| 115 void GotAudioFrame( | |
| 116 int* counter, | |
| 117 CastReceiver* cast_receiver, | |
| 118 scoped_ptr<AudioBus> audio_bus, | |
| 119 const base::TimeTicks& playout_time, | |
| 120 bool is_continuous) { | |
| 121 ++*counter; | |
| 122 cast_receiver->RequestDecodedAudioFrame( | |
| 123 base::Bind(&GotAudioFrame, counter, cast_receiver)); | |
| 124 } | |
| 125 | |
| 126 void AppendLog(EncodingEventSubscriber* subscriber, | |
| 127 const std::string& extra_data, | |
| 128 const base::FilePath& output_path) { | |
| 129 media::cast::proto::LogMetadata metadata; | |
| 130 metadata.set_extra_data(extra_data); | |
| 131 | |
| 132 media::cast::FrameEventList frame_events; | |
| 133 media::cast::PacketEventList packet_events; | |
| 134 subscriber->GetEventsAndReset( | |
| 135 &metadata, &frame_events, &packet_events); | |
| 136 media::cast::proto::GeneralDescription* gen_desc = | |
| 137 metadata.mutable_general_description(); | |
| 138 gen_desc->set_product("Cast Simulator"); | |
| 139 gen_desc->set_product_version("0.1"); | |
| 140 | |
| 141 scoped_ptr<char[]> serialized_log(new char[media::cast::kMaxSerializedBytes]); | |
| 142 int output_bytes; | |
| 143 bool success = media::cast::SerializeEvents(metadata, | |
| 144 frame_events, | |
| 145 packet_events, | |
| 146 true, | |
| 147 media::cast::kMaxSerializedBytes, | |
| 148 serialized_log.get(), | |
| 149 &output_bytes); | |
| 150 | |
| 151 if (!success) { | |
| 152 LOG(ERROR) << "Failed to serialize log."; | |
| 153 return; | |
| 154 } | |
| 155 | |
| 156 if (AppendToFile(output_path, serialized_log.get(), output_bytes) == -1) { | |
| 157 LOG(ERROR) << "Failed to append to log."; | |
| 158 } | |
| 159 } | |
| 160 | |
| 161 // Run simulation once. | |
| 162 // | |
| 163 // |output_path| is the path to write serialized log. | |
| 164 // |extra_data| is extra tagging information to write to log. | |
| 165 void RunSimulation(const base::FilePath& source_path, | |
| 166 const base::FilePath& output_path, | |
| 167 const std::string& extra_data, | |
| 168 const NetworkSimulationModel& model) { | |
| 169 // Fake clock. Make sure start time is non zero. | |
| 170 base::SimpleTestTickClock testing_clock; | |
| 171 testing_clock.Advance(base::TimeDelta::FromSeconds(1)); | |
| 172 | |
| 173 // Task runner. | |
| 174 scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner = | |
| 175 new test::FakeSingleThreadTaskRunner(&testing_clock); | |
| 176 base::ThreadTaskRunnerHandle task_runner_handle(task_runner); | |
| 177 | |
| 178 // CastEnvironments. | |
| 179 scoped_refptr<CastEnvironment> sender_env = | |
| 180 new CastEnvironment( | |
| 181 scoped_ptr<base::TickClock>( | |
| 182 new test::SkewedTickClock(&testing_clock)).Pass(), | |
| 183 task_runner, | |
| 184 task_runner, | |
| 185 task_runner); | |
| 186 scoped_refptr<CastEnvironment> receiver_env = | |
| 187 new CastEnvironment( | |
| 188 scoped_ptr<base::TickClock>( | |
| 189 new test::SkewedTickClock(&testing_clock)).Pass(), | |
| 190 task_runner, | |
| 191 task_runner, | |
| 192 task_runner); | |
| 193 | |
| 194 // Event subscriber. Store at most 1 hour of events. | |
| 195 EncodingEventSubscriber audio_event_subscriber(AUDIO_EVENT, | |
| 196 100 * 60 * 60); | |
| 197 EncodingEventSubscriber video_event_subscriber(VIDEO_EVENT, | |
| 198 30 * 60 * 60); | |
| 199 sender_env->Logging()->AddRawEventSubscriber(&audio_event_subscriber); | |
| 200 sender_env->Logging()->AddRawEventSubscriber(&video_event_subscriber); | |
| 201 | |
| 202 // Audio sender config. | |
| 203 AudioSenderConfig audio_sender_config = GetDefaultAudioSenderConfig(); | |
| 204 audio_sender_config.target_playout_delay = | |
| 205 base::TimeDelta::FromMilliseconds(kTargetDelay); | |
| 206 | |
| 207 // Audio receiver config. | |
| 208 FrameReceiverConfig audio_receiver_config = | |
| 209 GetDefaultAudioReceiverConfig(); | |
| 210 audio_receiver_config.rtp_max_delay_ms = | |
| 211 audio_sender_config.target_playout_delay.InMilliseconds(); | |
| 212 | |
| 213 // Video sender config. | |
| 214 VideoSenderConfig video_sender_config = GetDefaultVideoSenderConfig(); | |
| 215 video_sender_config.max_bitrate = 4000000; | |
| 216 video_sender_config.min_bitrate = 2000000; | |
| 217 video_sender_config.start_bitrate = 4000000; | |
| 218 video_sender_config.target_playout_delay = | |
| 219 base::TimeDelta::FromMilliseconds(kTargetDelay); | |
| 220 | |
| 221 // Video receiver config. | |
| 222 FrameReceiverConfig video_receiver_config = | |
| 223 GetDefaultVideoReceiverConfig(); | |
| 224 video_receiver_config.rtp_max_delay_ms = | |
| 225 video_sender_config.target_playout_delay.InMilliseconds(); | |
| 226 | |
| 227 // Loopback transport. | |
| 228 LoopBackTransport receiver_to_sender(receiver_env); | |
| 229 LoopBackTransport sender_to_receiver(sender_env); | |
| 230 | |
| 231 // Cast receiver. | |
| 232 scoped_ptr<CastReceiver> cast_receiver( | |
| 233 CastReceiver::Create(receiver_env, | |
| 234 audio_receiver_config, | |
| 235 video_receiver_config, | |
| 236 &receiver_to_sender)); | |
| 237 | |
| 238 // Cast sender and transport sender. | |
| 239 scoped_ptr<transport::CastTransportSender> transport_sender( | |
| 240 new transport::CastTransportSenderImpl( | |
| 241 NULL, | |
| 242 &testing_clock, | |
| 243 net::IPEndPoint(), | |
| 244 base::Bind(&UpdateCastTransportStatus), | |
| 245 base::Bind(&LogTransportEvents, sender_env), | |
| 246 base::TimeDelta::FromSeconds(1), | |
| 247 task_runner, | |
| 248 &sender_to_receiver)); | |
| 249 scoped_ptr<CastSender> cast_sender( | |
| 250 CastSender::Create(sender_env, transport_sender.get())); | |
| 251 | |
| 252 // Build packet pipe. | |
| 253 if (model.type() != media::cast::proto::INTERRUPTED_POISSON_PROCESS) { | |
| 254 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.
| |
| 255 return; | |
| 256 } | |
| 257 | |
| 258 const IPPModel& ipp_model = model.ipp(); | |
| 259 | |
| 260 std::vector<double> average_rates(ipp_model.average_rate_size()); | |
| 261 std::copy(ipp_model.average_rate().begin(), ipp_model.average_rate().end(), | |
| 262 average_rates.begin()); | |
| 263 test::InterruptedPoissonProcess ipp(average_rates, | |
| 264 ipp_model.coef_burstiness(), ipp_model.coef_variance(), 0); | |
| 265 | |
| 266 // Connect sender to receiver. This initializes the pipe. | |
| 267 receiver_to_sender.Initialize( | |
| 268 ipp.NewBuffer(128 * 1024), cast_sender->packet_receiver(), task_runner, | |
| 269 &testing_clock); | |
| 270 sender_to_receiver.Initialize( | |
| 271 ipp.NewBuffer(128 * 1024), cast_receiver->packet_receiver(), task_runner, | |
| 272 &testing_clock); | |
| 273 | |
| 274 // Start receiver. | |
| 275 int audio_frame_count = 0; | |
| 276 int video_frame_count = 0; | |
| 277 cast_receiver->RequestDecodedVideoFrame( | |
| 278 base::Bind(&GotVideoFrame, &video_frame_count, cast_receiver.get())); | |
| 279 cast_receiver->RequestDecodedAudioFrame( | |
| 280 base::Bind(&GotAudioFrame, &audio_frame_count, cast_receiver.get())); | |
| 281 | |
| 282 FakeMediaSource media_source(task_runner, | |
| 283 &testing_clock, | |
| 284 video_sender_config); | |
| 285 | |
| 286 // Initializing audio and video senders. | |
| 287 cast_sender->InitializeAudio(audio_sender_config, | |
| 288 base::Bind(&AudioInitializationStatus)); | |
| 289 cast_sender->InitializeVideo(media_source.get_video_config(), | |
| 290 base::Bind(&VideoInitializationStatus), | |
| 291 CreateDefaultVideoEncodeAcceleratorCallback(), | |
| 292 CreateDefaultVideoEncodeMemoryCallback()); | |
| 293 | |
| 294 // Start sending. | |
| 295 if (!source_path.empty()) { | |
| 296 // 0 means using the FPS from the file. | |
| 297 media_source.SetSourceFile(source_path, 0); | |
| 298 } | |
| 299 media_source.Start(cast_sender->audio_frame_input(), | |
| 300 cast_sender->video_frame_input()); | |
| 301 | |
| 302 // Run for 3 minutes. | |
| 303 base::TimeDelta elapsed_time; | |
| 304 while (elapsed_time.InMinutes() < 3) { | |
| 305 // Each step is 100us. | |
| 306 base::TimeDelta step = base::TimeDelta::FromMicroseconds(100); | |
| 307 task_runner->Sleep(step); | |
| 308 elapsed_time += step; | |
| 309 } | |
| 310 | |
| 311 LOG(INFO) << "Audio frame count: " << audio_frame_count; | |
| 312 LOG(INFO) << "Video frame count: " << video_frame_count; | |
| 313 LOG(INFO) << "Writing log: " << output_path.value(); | |
| 314 | |
| 315 // Truncate file and then write serialized log. | |
| 316 { | |
| 317 base::ScopedFILE file(base::OpenFile(output_path, "wb")); | |
| 318 if (!file.get()) { | |
| 319 LOG(INFO) << "Cannot write to log."; | |
| 320 return; | |
| 321 } | |
| 322 } | |
| 323 AppendLog(&video_event_subscriber, extra_data, output_path); | |
| 324 AppendLog(&audio_event_subscriber, extra_data, output_path); | |
| 325 } | |
| 326 | |
| 327 bool IsModelValid(const NetworkSimulationModel& model) { | |
| 328 if (!model.has_type()) | |
| 329 return false; | |
| 330 NetworkSimulationModelType type = model.type(); | |
| 331 if (type == media::cast::proto::INTERRUPTED_POISSON_PROCESS) { | |
| 332 if (!model.has_ipp()) | |
| 333 return false; | |
| 334 const IPPModel& ipp = model.ipp(); | |
| 335 if (ipp.coef_burstiness() <= 0.0 || ipp.coef_variance() <= 0.0) | |
| 336 return false; | |
| 337 if (ipp.average_rate_size() == 0) | |
| 338 return false; | |
| 339 for (int i = 0; i < ipp.average_rate_size(); i++) { | |
| 340 if (ipp.average_rate(i) <= 0.0) | |
| 341 return false; | |
| 342 } | |
| 343 } | |
| 344 | |
| 345 return true; | |
| 346 } | |
| 347 | |
| 348 } // namespace | |
| 349 } // namespace cast | |
| 350 } // namespace media | |
| 24 | 351 |
| 25 int main(int argc, char** argv) { | 352 int main(int argc, char** argv) { |
| 26 base::AtExitManager at_exit; | 353 base::AtExitManager at_exit; |
| 27 CommandLine::Init(argc, argv); | 354 CommandLine::Init(argc, argv); |
| 28 InitLogging(logging::LoggingSettings()); | 355 InitLogging(logging::LoggingSettings()); |
| 29 | 356 |
| 30 const CommandLine* cmd = CommandLine::ForCurrentProcess(); | 357 const CommandLine* cmd = CommandLine::ForCurrentProcess(); |
| 31 base::FilePath output_path = cmd->GetSwitchValuePath(kOutputPath); | 358 base::FilePath media_path = |
| 32 CHECK(!output_path.empty()); | 359 cmd->GetSwitchValuePath(media::cast::kFfmpegLibDir); |
| 33 std::string sim_run_id = cmd->GetSwitchValueASCII(kSimulationRunId); | 360 if (media_path.empty()) { |
| 361 if (!PathService::Get(base::DIR_MODULE, &media_path)) { | |
| 362 LOG(ERROR) << "Failed to load FFmpeg."; | |
| 363 return 1; | |
| 364 } | |
| 365 } | |
| 34 | 366 |
| 35 std::string msg = "Log from simulation run " + sim_run_id; | 367 if (!media::InitializeMediaLibrary(media_path)) { |
| 36 int ret = base::WriteFile(output_path, &msg[0], msg.size()); | 368 LOG(ERROR) << "Failed to initialize FFmpeg."; |
| 37 if (ret != static_cast<int>(msg.size())) | 369 return 1; |
| 38 VLOG(0) << "Failed to write logs to file."; | 370 } |
| 39 | 371 |
| 372 base::FilePath source_path = cmd->GetSwitchValuePath( | |
| 373 media::cast::kSourcePath); | |
| 374 base::FilePath output_path = cmd->GetSwitchValuePath( | |
| 375 media::cast::kOutputPath); | |
| 376 if (output_path.empty()) { | |
| 377 base::GetTempDir(&output_path); | |
| 378 output_path = output_path.AppendASCII("sim-events.gz"); | |
| 379 } | |
| 380 std::string sim_id = cmd->GetSwitchValueASCII(media::cast::kSimulationId); | |
| 381 | |
| 382 base::FilePath model_path = cmd->GetSwitchValuePath(media::cast::kModelPath); | |
| 383 if (model_path.empty()) { | |
| 384 LOG(ERROR) << "Model path not set."; | |
| 385 return 1; | |
| 386 } | |
| 387 std::string model_str; | |
| 388 if (!base::ReadFileToString(model_path, &model_str)) { | |
| 389 LOG(ERROR) << "Failed to read model file."; | |
| 390 return 1; | |
| 391 } | |
| 392 | |
| 393 NetworkSimulationModel model; | |
| 394 if (!model.ParseFromString(model_str)) { | |
| 395 LOG(ERROR) << "Failed to parse model."; | |
| 396 return 1; | |
| 397 } | |
| 398 if (!media::cast::IsModelValid(model)) { | |
| 399 LOG(ERROR) << "Invalid model."; | |
| 400 return 1; | |
| 401 } | |
| 402 | |
| 403 base::DictionaryValue values; | |
| 404 values.SetBoolean("sim", true); | |
| 405 values.SetString("sim-id", sim_id); | |
| 406 | |
| 407 std::string extra_data; | |
| 408 base::JSONWriter::Write(&values, &extra_data); | |
| 409 | |
| 410 // Run. | |
| 411 media::cast::RunSimulation(source_path, output_path, extra_data, model); | |
| 40 return 0; | 412 return 0; |
| 41 } | 413 } |
| OLD | NEW |