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 |