blob: 99d0a59deb006119ffe9b3cfeefc09d77e4b42e8 [file] [log] [blame]
// 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.
// Simulate end to end streaming.
//
// Input:
// --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.
// --target-delay-ms=
// Target playout delay to configure (integer number of milliseconds).
// Optional; default is 400.
// --max-frame-rate=
// The maximum frame rate allowed at any time during the Cast session.
// Optional; default is 30.
// --source-frame-rate=
// Overrides the playback rate; the source video will play faster/slower.
// --run-time=
// In seconds, how long the Cast session runs for.
// Optional; default is 180.
// --metrics-output=
// File path to write PSNR and SSIM metrics between source frames and
// decoded frames. Assumes all encoded frames are decoded.
// --yuv-output=
// File path to write YUV decoded frames in YUV4MPEG2 format.
// --no-simulation
// Do not run network simulation.
//
// Output:
// - Raw event log of the simulation session tagged with the unique test ID,
// written out to the specified file path.
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/at_exit.h"
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/containers/queue.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.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/macros.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/tick_clock.h"
#include "base/values.h"
#include "media/base/audio_bus.h"
#include "media/base/fake_single_thread_task_runner.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_sender.h"
#include "media/cast/logging/encoding_event_subscriber.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/net/cast_transport.h"
#include "media/cast/net/cast_transport_config.h"
#include "media/cast/net/cast_transport_defines.h"
#include "media/cast/net/cast_transport_impl.h"
#include "media/cast/test/fake_media_source.h"
#include "media/cast/test/loopback_transport.h"
#include "media/cast/test/proto/network_simulation_model.pb.h"
#include "media/cast/test/receiver/cast_receiver.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"
using media::cast::proto::IPPModel;
using media::cast::proto::NetworkSimulationModel;
using media::cast::proto::NetworkSimulationModelType;
namespace media {
namespace cast {
namespace {
const char kLibDir[] = "lib-dir";
const char kModelPath[] = "model";
const char kMetricsOutputPath[] = "metrics-output";
const char kOutputPath[] = "output";
const char kMaxFrameRate[] = "max-frame-rate";
const char kNoSimulation[] = "no-simulation";
const char kRunTime[] = "run-time";
const char kSimulationId[] = "sim-id";
const char kSourcePath[] = "source";
const char kSourceFrameRate[] = "source-frame-rate";
const char kTargetDelay[] = "target-delay-ms";
const char kYuvOutputPath[] = "yuv-output";
int GetIntegerSwitchValue(const char* switch_name, int default_value) {
const std::string as_str =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(switch_name);
if (as_str.empty())
return default_value;
int as_int;
CHECK(base::StringToInt(as_str, &as_int));
CHECK_GT(as_int, 0);
return as_int;
}
void LogAudioOperationalStatus(OperationalStatus status) {
LOG(INFO) << "Audio status: " << status;
}
void LogVideoOperationalStatus(OperationalStatus status) {
LOG(INFO) << "Video status: " << status;
}
struct PacketProxy {
PacketProxy() : receiver(nullptr) {}
void ReceivePacket(std::unique_ptr<Packet> packet) {
if (receiver)
receiver->ReceivePacket(std::move(packet));
}
CastReceiver* receiver;
};
class TransportClient : public CastTransport::Client {
public:
TransportClient(LogEventDispatcher* log_event_dispatcher,
PacketProxy* packet_proxy)
: log_event_dispatcher_(log_event_dispatcher),
packet_proxy_(packet_proxy) {}
void OnStatusChanged(CastTransportStatus status) final {
LOG(INFO) << "Cast transport status: " << status;
}
void OnLoggingEventsReceived(
std::unique_ptr<std::vector<FrameEvent>> frame_events,
std::unique_ptr<std::vector<PacketEvent>> packet_events) final {
DCHECK(log_event_dispatcher_);
log_event_dispatcher_->DispatchBatchOfEvents(std::move(frame_events),
std::move(packet_events));
}
void ProcessRtpPacket(std::unique_ptr<Packet> packet) final {
if (packet_proxy_)
packet_proxy_->ReceivePacket(std::move(packet));
}
private:
LogEventDispatcher* const log_event_dispatcher_; // Not owned by this class.
PacketProxy* const packet_proxy_; // Not owned by this class.
DISALLOW_COPY_AND_ASSIGN(TransportClient);
};
// Maintains a queue of encoded video frames.
// This works by tracking FRAME_CAPTURE_END and FRAME_ENCODED events.
// If a video frame is detected to be encoded it transfers a frame
// from FakeMediaSource to its internal queue. Otherwise it drops a
// frame from FakeMediaSource.
class EncodedVideoFrameTracker final : public RawEventSubscriber {
public:
EncodedVideoFrameTracker(FakeMediaSource* media_source)
: media_source_(media_source),
last_frame_event_type_(UNKNOWN) {}
EncodedVideoFrameTracker(const EncodedVideoFrameTracker&) = delete;
EncodedVideoFrameTracker& operator=(const EncodedVideoFrameTracker&) = delete;
~EncodedVideoFrameTracker() override = default;
// RawEventSubscriber implementations.
void OnReceiveFrameEvent(const FrameEvent& frame_event) override {
// This method only cares about video FRAME_CAPTURE_END and
// FRAME_ENCODED events.
if (frame_event.media_type != VIDEO_EVENT) {
return;
}
if (frame_event.type != FRAME_CAPTURE_END &&
frame_event.type != FRAME_ENCODED) {
return;
}
// If there are two consecutive FRAME_CAPTURE_END events that means
// a frame is dropped.
if (last_frame_event_type_ == FRAME_CAPTURE_END &&
frame_event.type == FRAME_CAPTURE_END) {
media_source_->PopOldestInsertedVideoFrame();
}
if (frame_event.type == FRAME_ENCODED) {
video_frames_.push(media_source_->PopOldestInsertedVideoFrame());
}
last_frame_event_type_ = frame_event.type;
}
void OnReceivePacketEvent(const PacketEvent& packet_event) override {
// Don't care.
}
scoped_refptr<media::VideoFrame> PopOldestEncodedFrame() {
CHECK(!video_frames_.empty());
scoped_refptr<media::VideoFrame> video_frame = video_frames_.front();
video_frames_.pop();
return video_frame;
}
private:
FakeMediaSource* media_source_;
CastLoggingEvent last_frame_event_type_;
base::queue<scoped_refptr<media::VideoFrame>> video_frames_;
};
// Appends a YUV frame in I420 format to the file located at |path|.
void AppendYuvToFile(const base::FilePath& path,
scoped_refptr<media::VideoFrame> frame) {
// Write YUV420 format to file.
std::string header;
base::StringAppendF(
&header, "FRAME W%d H%d\n",
frame->coded_size().width(),
frame->coded_size().height());
AppendToFile(path, header);
AppendToFile(path,
base::make_span(frame->data(media::VideoFrame::kYPlane),
frame->stride(media::VideoFrame::kYPlane) *
frame->rows(media::VideoFrame::kYPlane)));
AppendToFile(path,
base::make_span(frame->data(media::VideoFrame::kUPlane),
frame->stride(media::VideoFrame::kUPlane) *
frame->rows(media::VideoFrame::kUPlane)));
AppendToFile(path,
base::make_span(frame->data(media::VideoFrame::kVPlane),
frame->stride(media::VideoFrame::kVPlane) *
frame->rows(media::VideoFrame::kVPlane)));
}
// A container to save output of GotVideoFrame() for computation based
// on output frames.
struct GotVideoFrameOutput {
GotVideoFrameOutput() : counter(0) {}
int counter;
std::vector<double> psnr;
std::vector<double> ssim;
};
void GotVideoFrame(GotVideoFrameOutput* metrics_output,
const base::FilePath& yuv_output,
EncodedVideoFrameTracker* video_frame_tracker,
CastReceiver* cast_receiver,
scoped_refptr<media::VideoFrame> video_frame,
base::TimeTicks render_time,
bool continuous) {
++metrics_output->counter;
cast_receiver->RequestDecodedVideoFrame(
base::BindRepeating(&GotVideoFrame, metrics_output, yuv_output,
video_frame_tracker, cast_receiver));
// If |video_frame_tracker| is available that means we're computing
// quality metrices.
if (video_frame_tracker) {
scoped_refptr<media::VideoFrame> src_frame =
video_frame_tracker->PopOldestEncodedFrame();
metrics_output->psnr.push_back(I420PSNR(*src_frame, *video_frame));
metrics_output->ssim.push_back(I420SSIM(*src_frame, *video_frame));
}
if (!yuv_output.empty()) {
AppendYuvToFile(yuv_output, std::move(video_frame));
}
}
void GotAudioFrame(int* counter,
CastReceiver* cast_receiver,
std::unique_ptr<AudioBus> audio_bus,
base::TimeTicks playout_time,
bool is_continuous) {
++*counter;
cast_receiver->RequestDecodedAudioFrame(
base::BindRepeating(&GotAudioFrame, counter, cast_receiver));
}
// Run simulation once.
//
// |log_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& log_output_path,
const base::FilePath& metrics_output_path,
const base::FilePath& yuv_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::Seconds(1));
// Task runner.
scoped_refptr<FakeSingleThreadTaskRunner> task_runner =
new FakeSingleThreadTaskRunner(&testing_clock);
base::ThreadTaskRunnerHandle task_runner_handle(task_runner);
// CastEnvironments.
test::SkewedTickClock sender_clock(&testing_clock);
scoped_refptr<CastEnvironment> sender_env =
new CastEnvironment(&sender_clock, task_runner, task_runner, task_runner);
test::SkewedTickClock receiver_clock(&testing_clock);
scoped_refptr<CastEnvironment> receiver_env = new CastEnvironment(
&receiver_clock, 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->logger()->Subscribe(&audio_event_subscriber);
sender_env->logger()->Subscribe(&video_event_subscriber);
// Audio sender config.
FrameSenderConfig audio_sender_config = GetDefaultAudioSenderConfig();
audio_sender_config.min_playout_delay =
audio_sender_config.max_playout_delay =
base::Milliseconds(GetIntegerSwitchValue(kTargetDelay, 400));
// Audio receiver config.
FrameReceiverConfig audio_receiver_config =
GetDefaultAudioReceiverConfig();
audio_receiver_config.rtp_max_delay_ms =
audio_sender_config.max_playout_delay.InMilliseconds();
// Video sender config.
FrameSenderConfig video_sender_config = GetDefaultVideoSenderConfig();
video_sender_config.max_bitrate = 2500000;
video_sender_config.min_bitrate = 2000000;
video_sender_config.start_bitrate = 2000000;
video_sender_config.min_playout_delay =
video_sender_config.max_playout_delay =
audio_sender_config.max_playout_delay;
video_sender_config.max_frame_rate = GetIntegerSwitchValue(kMaxFrameRate, 30);
// Video receiver config.
FrameReceiverConfig video_receiver_config =
GetDefaultVideoReceiverConfig();
video_receiver_config.rtp_max_delay_ms =
video_sender_config.max_playout_delay.InMilliseconds();
// Loopback transport. Owned by CastTransport.
LoopBackTransport* receiver_to_sender = new LoopBackTransport(receiver_env);
LoopBackTransport* sender_to_receiver = new LoopBackTransport(sender_env);
PacketProxy packet_proxy;
// Cast receiver.
std::unique_ptr<CastTransport> transport_receiver(new CastTransportImpl(
&testing_clock, base::Seconds(1),
std::make_unique<TransportClient>(receiver_env->logger(), &packet_proxy),
base::WrapUnique(receiver_to_sender), task_runner));
std::unique_ptr<CastReceiver> cast_receiver(
CastReceiver::Create(receiver_env, audio_receiver_config,
video_receiver_config, transport_receiver.get()));
packet_proxy.receiver = cast_receiver.get();
// Cast sender and transport sender.
std::unique_ptr<CastTransport> transport_sender(new CastTransportImpl(
&testing_clock, base::Seconds(1),
std::make_unique<TransportClient>(sender_env->logger(), nullptr),
base::WrapUnique(sender_to_receiver), task_runner));
std::unique_ptr<CastSender> cast_sender(
CastSender::Create(sender_env, transport_sender.get()));
// Initialize network simulation model.
const bool use_network_simulation =
model.type() == media::cast::proto::INTERRUPTED_POISSON_PROCESS;
std::unique_ptr<test::InterruptedPoissonProcess> ipp;
if (use_network_simulation) {
LOG(INFO) << "Running Poisson based network simulation.";
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());
ipp = std::make_unique<test::InterruptedPoissonProcess>(
average_rates, ipp_model.coef_burstiness(), ipp_model.coef_variance(),
0);
receiver_to_sender->Initialize(ipp->NewBuffer(128 * 1024),
transport_sender->PacketReceiverForTesting(),
task_runner, &testing_clock);
sender_to_receiver->Initialize(
ipp->NewBuffer(128 * 1024),
transport_receiver->PacketReceiverForTesting(), task_runner,
&testing_clock);
} else {
LOG(INFO) << "No network simulation.";
receiver_to_sender->Initialize(std::unique_ptr<test::PacketPipe>(),
transport_sender->PacketReceiverForTesting(),
task_runner, &testing_clock);
sender_to_receiver->Initialize(
std::unique_ptr<test::PacketPipe>(),
transport_receiver->PacketReceiverForTesting(), task_runner,
&testing_clock);
}
// Initialize a fake media source and a tracker to encoded video frames.
const bool quality_test = !metrics_output_path.empty();
FakeMediaSource media_source(task_runner,
&testing_clock,
audio_sender_config,
video_sender_config,
quality_test);
std::unique_ptr<EncodedVideoFrameTracker> video_frame_tracker;
if (quality_test) {
video_frame_tracker =
std::make_unique<EncodedVideoFrameTracker>(&media_source);
sender_env->logger()->Subscribe(video_frame_tracker.get());
}
// Quality metrics computed for each frame decoded.
GotVideoFrameOutput metrics_output;
// Start receiver.
int audio_frame_count = 0;
cast_receiver->RequestDecodedVideoFrame(
base::BindRepeating(&GotVideoFrame, &metrics_output, yuv_output_path,
video_frame_tracker.get(), cast_receiver.get()));
cast_receiver->RequestDecodedAudioFrame(base::BindRepeating(
&GotAudioFrame, &audio_frame_count, cast_receiver.get()));
// Initializing audio and video senders.
cast_sender->InitializeAudio(audio_sender_config,
base::BindOnce(&LogAudioOperationalStatus));
cast_sender->InitializeVideo(media_source.get_video_config(),
base::BindRepeating(&LogVideoOperationalStatus),
base::DoNothing());
task_runner->RunTasks();
// Truncate YUV files to prepare for writing.
if (!yuv_output_path.empty()) {
base::ScopedFILE file(base::OpenFile(yuv_output_path, "wb"));
if (!file.get()) {
LOG(ERROR) << "Cannot save YUV output to file.";
return;
}
LOG(INFO) << "Writing YUV output to file: " << yuv_output_path.value();
// Write YUV4MPEG2 header.
const std::string header("YUV4MPEG2 W1280 H720 F30000:1001 Ip A1:1 C420\n");
AppendToFile(yuv_output_path, header);
}
// Start sending.
if (!source_path.empty()) {
// 0 means using the FPS from the file.
media_source.SetSourceFile(source_path,
GetIntegerSwitchValue(kSourceFrameRate, 0));
}
media_source.Start(cast_sender->audio_frame_input(),
cast_sender->video_frame_input());
// By default runs simulation for 3 minutes or the desired duration
// by using --run-time= flag.
base::TimeDelta elapsed_time;
const base::TimeDelta desired_run_time =
base::Seconds(GetIntegerSwitchValue(kRunTime, 180));
while (elapsed_time < desired_run_time) {
// Each step is 100us.
base::TimeDelta step = base::Microseconds(100);
task_runner->Sleep(step);
elapsed_time += step;
}
// Unsubscribe from logging events.
sender_env->logger()->Unsubscribe(&audio_event_subscriber);
sender_env->logger()->Unsubscribe(&video_event_subscriber);
if (quality_test)
sender_env->logger()->Unsubscribe(video_frame_tracker.get());
// Get event logs for audio and video.
media::cast::proto::LogMetadata audio_metadata, video_metadata;
media::cast::FrameEventList audio_frame_events, video_frame_events;
media::cast::PacketEventList audio_packet_events, video_packet_events;
audio_metadata.set_extra_data(extra_data);
video_metadata.set_extra_data(extra_data);
audio_event_subscriber.GetEventsAndReset(
&audio_metadata, &audio_frame_events, &audio_packet_events);
video_event_subscriber.GetEventsAndReset(
&video_metadata, &video_frame_events, &video_packet_events);
// Print simulation results.
// Compute and print statistics for video:
//
// * Total video frames captured.
// * Total video frames encoded.
// * Total video frames dropped.
// * Total video frames received late.
// * Average target bitrate.
// * Average encoded bitrate.
int total_video_frames = 0;
int encoded_video_frames = 0;
int dropped_video_frames = 0;
int late_video_frames = 0;
int64_t total_delay_of_late_frames_ms = 0;
int64_t encoded_size = 0;
int64_t target_bitrate = 0;
for (size_t i = 0; i < video_frame_events.size(); ++i) {
const media::cast::proto::AggregatedFrameEvent& event =
*video_frame_events[i];
++total_video_frames;
if (event.has_encoded_frame_size()) {
++encoded_video_frames;
encoded_size += event.encoded_frame_size();
target_bitrate += event.target_bitrate();
} else {
++dropped_video_frames;
}
if (event.has_delay_millis() && event.delay_millis() < 0) {
++late_video_frames;
total_delay_of_late_frames_ms += -event.delay_millis();
}
}
// Subtract fraction of dropped frames from |elapsed_time| before estimating
// the average encoded bitrate.
const base::TimeDelta elapsed_time_undropped =
total_video_frames <= 0
? base::TimeDelta()
: (elapsed_time * (total_video_frames - dropped_video_frames) /
total_video_frames);
constexpr double kKilobitsPerByte = 8.0 / 1000;
const double avg_encoded_bitrate =
elapsed_time_undropped <= base::TimeDelta()
? 0
: encoded_size * kKilobitsPerByte * elapsed_time_undropped.ToHz();
double avg_target_bitrate =
encoded_video_frames ? target_bitrate / encoded_video_frames / 1000 : 0;
LOG(INFO) << "Configured target playout delay (ms): "
<< video_receiver_config.rtp_max_delay_ms;
LOG(INFO) << "Audio frame count: " << audio_frame_count;
LOG(INFO) << "Inserted video frames: " << total_video_frames;
LOG(INFO) << "Decoded video frames: " << metrics_output.counter;
LOG(INFO) << "Dropped video frames: " << dropped_video_frames;
LOG(INFO) << "Late video frames: " << late_video_frames
<< " (average lateness: "
<< (late_video_frames > 0 ?
static_cast<double>(total_delay_of_late_frames_ms) /
late_video_frames :
0)
<< " ms)";
LOG(INFO) << "Average encoded bitrate (kbps): " << avg_encoded_bitrate;
LOG(INFO) << "Average target bitrate (kbps): " << avg_target_bitrate;
LOG(INFO) << "Writing log: " << log_output_path.value();
// Truncate file and then write serialized log.
{
base::ScopedFILE file(base::OpenFile(log_output_path, "wb"));
if (!file.get()) {
LOG(INFO) << "Cannot write to log.";
return;
}
}
// Write quality metrics.
if (quality_test) {
LOG(INFO) << "Writing quality metrics: " << metrics_output_path.value();
std::string line;
for (size_t i = 0; i < metrics_output.psnr.size() &&
i < metrics_output.ssim.size(); ++i) {
base::StringAppendF(&line, "%f %f\n", metrics_output.psnr[i],
metrics_output.ssim[i]);
}
WriteFile(metrics_output_path, line.data(), line.length());
}
}
NetworkSimulationModel DefaultModel() {
NetworkSimulationModel model;
model.set_type(cast::proto::INTERRUPTED_POISSON_PROCESS);
IPPModel* ipp = model.mutable_ipp();
ipp->set_coef_burstiness(0.609);
ipp->set_coef_variance(4.1);
ipp->add_average_rate(0.609);
ipp->add_average_rate(0.495);
ipp->add_average_rate(0.561);
ipp->add_average_rate(0.458);
ipp->add_average_rate(0.538);
ipp->add_average_rate(0.513);
ipp->add_average_rate(0.585);
ipp->add_average_rate(0.592);
ipp->add_average_rate(0.658);
ipp->add_average_rate(0.556);
ipp->add_average_rate(0.371);
ipp->add_average_rate(0.595);
ipp->add_average_rate(0.490);
ipp->add_average_rate(0.980);
ipp->add_average_rate(0.781);
ipp->add_average_rate(0.463);
return model;
}
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;
}
NetworkSimulationModel LoadModel(const base::FilePath& model_path) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(kNoSimulation)) {
NetworkSimulationModel model;
model.set_type(media::cast::proto::NO_SIMULATION);
return model;
}
if (model_path.empty()) {
LOG(ERROR) << "Model path not set; Using default model.";
return DefaultModel();
}
std::string model_str;
if (!base::ReadFileToString(model_path, &model_str)) {
LOG(ERROR) << "Failed to read model file.";
return DefaultModel();
}
NetworkSimulationModel model;
if (!model.ParseFromString(model_str)) {
LOG(ERROR) << "Failed to parse model.";
return DefaultModel();
}
if (!IsModelValid(model)) {
LOG(ERROR) << "Invalid model.";
return DefaultModel();
}
return model;
}
} // namespace
} // namespace cast
} // namespace media
int main(int argc, char** argv) {
base::AtExitManager at_exit;
base::CommandLine::Init(argc, argv);
InitLogging(logging::LoggingSettings());
const base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
base::FilePath media_path = cmd->GetSwitchValuePath(media::cast::kLibDir);
if (media_path.empty()) {
if (!base::PathService::Get(base::DIR_MODULE, &media_path)) {
LOG(ERROR) << "Failed to load FFmpeg.";
return 1;
}
}
media::InitializeMediaLibrary();
base::FilePath source_path = cmd->GetSwitchValuePath(
media::cast::kSourcePath);
base::FilePath log_output_path = cmd->GetSwitchValuePath(
media::cast::kOutputPath);
if (log_output_path.empty()) {
base::GetTempDir(&log_output_path);
log_output_path = log_output_path.AppendASCII("sim-events.gz");
}
base::FilePath metrics_output_path = cmd->GetSwitchValuePath(
media::cast::kMetricsOutputPath);
base::FilePath yuv_output_path = cmd->GetSwitchValuePath(
media::cast::kYuvOutputPath);
std::string sim_id = cmd->GetSwitchValueASCII(media::cast::kSimulationId);
NetworkSimulationModel model = media::cast::LoadModel(
cmd->GetSwitchValuePath(media::cast::kModelPath));
base::DictionaryValue values;
values.SetBoolean("sim", true);
values.SetString("sim-id", sim_id);
std::string extra_data;
base::JSONWriter::Write(values, &extra_data);
// Run.
media::cast::RunSimulation(source_path, log_output_path, metrics_output_path,
yuv_output_path, extra_data, model);
return 0;
}