Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(110)

Unified Diff: media/cast/test/simulator.cc

Issue 362123005: Cast: Update simulator tool with more inputs. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Diff Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/cast/test/proto/network_simulation_model.proto ('k') | media/cast/test/utility/default_config.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
}
« no previous file with comments | « media/cast/test/proto/network_simulation_model.proto ('k') | media/cast/test/utility/default_config.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698