blob: e26cb8535f80b6693cf1dd1152aa72dc0723fcf9 [file] [log] [blame]
// Copyright 2013 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.
// Test application that simulates a cast sender - Data can be either generated
// or read from a file.
#include <queue>
#include "base/at_exit.h"
#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread.h"
#include "base/time/default_tick_clock.h"
#include "base/values.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/log_serializer.h"
#include "media/cast/logging/logging_defines.h"
#include "media/cast/logging/proto/raw_events.pb.h"
#include "media/cast/logging/receiver_time_offset_estimator_impl.h"
#include "media/cast/logging/stats_event_subscriber.h"
#include "media/cast/net/cast_transport_defines.h"
#include "media/cast/net/cast_transport_sender.h"
#include "media/cast/net/udp_transport.h"
#include "media/cast/test/fake_media_source.h"
#include "media/cast/test/utility/default_config.h"
#include "media/cast/test/utility/input_builder.h"
namespace {
// The max allowed size of serialized log.
const int kMaxSerializedLogBytes = 10 * 1000 * 1000;
// Flags for this program:
//
// --address=xx.xx.xx.xx
// IP address of receiver.
//
// --port=xxxx
// Port number of receiver.
//
// --source-file=xxx.webm
// WebM file as source of video frames.
//
// --fps=xx
// Override framerate of the video stream.
//
// --vary-frame-sizes
// Randomly vary the video frame sizes at random points in time. Has no
// effect if --source-file is being used.
const char kSwitchAddress[] = "address";
const char kSwitchPort[] = "port";
const char kSwitchSourceFile[] = "source-file";
const char kSwitchFps[] = "fps";
const char kSwitchVaryFrameSizes[] = "vary-frame-sizes";
void UpdateCastTransportStatus(
media::cast::CastTransportStatus status) {
VLOG(1) << "Transport status: " << status;
}
void LogRawEvents(
const scoped_refptr<media::cast::CastEnvironment>& cast_environment,
const std::vector<media::cast::PacketEvent>& packet_events,
const std::vector<media::cast::FrameEvent>& frame_events) {
VLOG(1) << "Got packet events from transport, size: " << packet_events.size();
for (std::vector<media::cast::PacketEvent>::const_iterator it =
packet_events.begin();
it != packet_events.end();
++it) {
cast_environment->Logging()->InsertPacketEvent(it->timestamp,
it->type,
it->media_type,
it->rtp_timestamp,
it->frame_id,
it->packet_id,
it->max_packet_id,
it->size);
}
VLOG(1) << "Got frame events from transport, size: " << frame_events.size();
for (std::vector<media::cast::FrameEvent>::const_iterator it =
frame_events.begin();
it != frame_events.end();
++it) {
cast_environment->Logging()->InsertFrameEvent(it->timestamp,
it->type,
it->media_type,
it->rtp_timestamp,
it->frame_id);
}
}
void QuitLoopOnInitializationResult(media::cast::OperationalStatus result) {
CHECK(result == media::cast::STATUS_INITIALIZED)
<< "Cast sender uninitialized";
base::MessageLoop::current()->Quit();
}
net::IPEndPoint CreateUDPAddress(std::string ip_str, uint16 port) {
net::IPAddressNumber ip_number;
CHECK(net::ParseIPLiteralToNumber(ip_str, &ip_number));
return net::IPEndPoint(ip_number, port);
}
void DumpLoggingData(const media::cast::proto::LogMetadata& log_metadata,
const media::cast::FrameEventList& frame_events,
const media::cast::PacketEventList& packet_events,
base::ScopedFILE log_file) {
VLOG(0) << "Frame map size: " << frame_events.size();
VLOG(0) << "Packet map size: " << packet_events.size();
scoped_ptr<char[]> event_log(new char[kMaxSerializedLogBytes]);
int event_log_bytes;
if (!media::cast::SerializeEvents(log_metadata,
frame_events,
packet_events,
true,
kMaxSerializedLogBytes,
event_log.get(),
&event_log_bytes)) {
VLOG(0) << "Failed to serialize events.";
return;
}
VLOG(0) << "Events serialized length: " << event_log_bytes;
int ret = fwrite(event_log.get(), 1, event_log_bytes, log_file.get());
if (ret != event_log_bytes)
VLOG(0) << "Failed to write logs to file.";
}
void WriteLogsToFileAndDestroySubscribers(
const scoped_refptr<media::cast::CastEnvironment>& cast_environment,
scoped_ptr<media::cast::EncodingEventSubscriber> video_event_subscriber,
scoped_ptr<media::cast::EncodingEventSubscriber> audio_event_subscriber,
base::ScopedFILE video_log_file,
base::ScopedFILE audio_log_file) {
cast_environment->Logging()->RemoveRawEventSubscriber(
video_event_subscriber.get());
cast_environment->Logging()->RemoveRawEventSubscriber(
audio_event_subscriber.get());
VLOG(0) << "Dumping logging data for video stream.";
media::cast::proto::LogMetadata log_metadata;
media::cast::FrameEventList frame_events;
media::cast::PacketEventList packet_events;
video_event_subscriber->GetEventsAndReset(
&log_metadata, &frame_events, &packet_events);
DumpLoggingData(log_metadata,
frame_events,
packet_events,
video_log_file.Pass());
VLOG(0) << "Dumping logging data for audio stream.";
audio_event_subscriber->GetEventsAndReset(
&log_metadata, &frame_events, &packet_events);
DumpLoggingData(log_metadata,
frame_events,
packet_events,
audio_log_file.Pass());
}
void WriteStatsAndDestroySubscribers(
const scoped_refptr<media::cast::CastEnvironment>& cast_environment,
scoped_ptr<media::cast::StatsEventSubscriber> video_event_subscriber,
scoped_ptr<media::cast::StatsEventSubscriber> audio_event_subscriber,
scoped_ptr<media::cast::ReceiverTimeOffsetEstimatorImpl> estimator) {
cast_environment->Logging()->RemoveRawEventSubscriber(
video_event_subscriber.get());
cast_environment->Logging()->RemoveRawEventSubscriber(
audio_event_subscriber.get());
cast_environment->Logging()->RemoveRawEventSubscriber(estimator.get());
scoped_ptr<base::DictionaryValue> stats = video_event_subscriber->GetStats();
std::string json;
base::JSONWriter::WriteWithOptions(
stats.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
VLOG(0) << "Video stats: " << json;
stats = audio_event_subscriber->GetStats();
json.clear();
base::JSONWriter::WriteWithOptions(
stats.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
VLOG(0) << "Audio stats: " << json;
}
} // namespace
int main(int argc, char** argv) {
base::AtExitManager at_exit;
base::CommandLine::Init(argc, argv);
InitLogging(logging::LoggingSettings());
// Prepare media module for FFmpeg decoding.
media::InitializeMediaLibrary();
base::Thread test_thread("Cast sender test app thread");
base::Thread audio_thread("Cast audio encoder thread");
base::Thread video_thread("Cast video encoder thread");
test_thread.Start();
audio_thread.Start();
video_thread.Start();
base::MessageLoopForIO io_message_loop;
// Default parameters.
base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
std::string remote_ip_address = cmd->GetSwitchValueASCII(kSwitchAddress);
if (remote_ip_address.empty())
remote_ip_address = "127.0.0.1";
int remote_port = 0;
if (!base::StringToInt(cmd->GetSwitchValueASCII(kSwitchPort), &remote_port) ||
remote_port < 0 || remote_port > 65535) {
remote_port = 2344;
}
LOG(INFO) << "Sending to " << remote_ip_address << ":" << remote_port
<< ".";
media::cast::AudioSenderConfig audio_config =
media::cast::GetDefaultAudioSenderConfig();
media::cast::VideoSenderConfig video_config =
media::cast::GetDefaultVideoSenderConfig();
// Running transport on the main thread.
// Setting up transport config.
net::IPEndPoint remote_endpoint =
CreateUDPAddress(remote_ip_address, static_cast<uint16>(remote_port));
// Enable raw event and stats logging.
// Running transport on the main thread.
scoped_refptr<media::cast::CastEnvironment> cast_environment(
new media::cast::CastEnvironment(
make_scoped_ptr<base::TickClock>(new base::DefaultTickClock()),
io_message_loop.message_loop_proxy(),
audio_thread.message_loop_proxy(),
video_thread.message_loop_proxy()));
// SendProcess initialization.
scoped_ptr<media::cast::FakeMediaSource> fake_media_source(
new media::cast::FakeMediaSource(test_thread.message_loop_proxy(),
cast_environment->Clock(),
audio_config,
video_config,
false));
int final_fps = 0;
if (!base::StringToInt(cmd->GetSwitchValueASCII(kSwitchFps),
&final_fps)){
final_fps = 0;
}
base::FilePath source_path = cmd->GetSwitchValuePath(kSwitchSourceFile);
if (!source_path.empty()) {
LOG(INFO) << "Source: " << source_path.value();
fake_media_source->SetSourceFile(source_path, final_fps);
}
if (cmd->HasSwitch(kSwitchVaryFrameSizes))
fake_media_source->SetVariableFrameSizeMode(true);
// CastTransportSender initialization.
scoped_ptr<media::cast::CastTransportSender> transport_sender =
media::cast::CastTransportSender::Create(
NULL, // net log.
cast_environment->Clock(),
net::IPEndPoint(),
remote_endpoint,
make_scoped_ptr(new base::DictionaryValue), // options
base::Bind(&UpdateCastTransportStatus),
base::Bind(&LogRawEvents, cast_environment),
base::TimeDelta::FromSeconds(1),
media::cast::PacketReceiverCallback(),
io_message_loop.message_loop_proxy());
// Set up event subscribers.
scoped_ptr<media::cast::EncodingEventSubscriber> video_event_subscriber;
scoped_ptr<media::cast::EncodingEventSubscriber> audio_event_subscriber;
std::string video_log_file_name("/tmp/video_events.log.gz");
std::string audio_log_file_name("/tmp/audio_events.log.gz");
LOG(INFO) << "Logging audio events to: " << audio_log_file_name;
LOG(INFO) << "Logging video events to: " << video_log_file_name;
video_event_subscriber.reset(new media::cast::EncodingEventSubscriber(
media::cast::VIDEO_EVENT, 10000));
audio_event_subscriber.reset(new media::cast::EncodingEventSubscriber(
media::cast::AUDIO_EVENT, 10000));
cast_environment->Logging()->AddRawEventSubscriber(
video_event_subscriber.get());
cast_environment->Logging()->AddRawEventSubscriber(
audio_event_subscriber.get());
// Subscribers for stats.
scoped_ptr<media::cast::ReceiverTimeOffsetEstimatorImpl> offset_estimator(
new media::cast::ReceiverTimeOffsetEstimatorImpl());
cast_environment->Logging()->AddRawEventSubscriber(offset_estimator.get());
scoped_ptr<media::cast::StatsEventSubscriber> video_stats_subscriber(
new media::cast::StatsEventSubscriber(media::cast::VIDEO_EVENT,
cast_environment->Clock(),
offset_estimator.get()));
scoped_ptr<media::cast::StatsEventSubscriber> audio_stats_subscriber(
new media::cast::StatsEventSubscriber(media::cast::AUDIO_EVENT,
cast_environment->Clock(),
offset_estimator.get()));
cast_environment->Logging()->AddRawEventSubscriber(
video_stats_subscriber.get());
cast_environment->Logging()->AddRawEventSubscriber(
audio_stats_subscriber.get());
base::ScopedFILE video_log_file(fopen(video_log_file_name.c_str(), "w"));
if (!video_log_file) {
VLOG(1) << "Failed to open video log file for writing.";
exit(-1);
}
base::ScopedFILE audio_log_file(fopen(audio_log_file_name.c_str(), "w"));
if (!audio_log_file) {
VLOG(1) << "Failed to open audio log file for writing.";
exit(-1);
}
const int logging_duration_seconds = 10;
io_message_loop.message_loop_proxy()->PostDelayedTask(
FROM_HERE,
base::Bind(&WriteLogsToFileAndDestroySubscribers,
cast_environment,
base::Passed(&video_event_subscriber),
base::Passed(&audio_event_subscriber),
base::Passed(&video_log_file),
base::Passed(&audio_log_file)),
base::TimeDelta::FromSeconds(logging_duration_seconds));
io_message_loop.message_loop_proxy()->PostDelayedTask(
FROM_HERE,
base::Bind(&WriteStatsAndDestroySubscribers,
cast_environment,
base::Passed(&video_stats_subscriber),
base::Passed(&audio_stats_subscriber),
base::Passed(&offset_estimator)),
base::TimeDelta::FromSeconds(logging_duration_seconds));
// CastSender initialization.
scoped_ptr<media::cast::CastSender> cast_sender =
media::cast::CastSender::Create(cast_environment, transport_sender.get());
io_message_loop.PostTask(
FROM_HERE,
base::Bind(&media::cast::CastSender::InitializeVideo,
base::Unretained(cast_sender.get()),
fake_media_source->get_video_config(),
base::Bind(&QuitLoopOnInitializationResult),
media::cast::CreateDefaultVideoEncodeAcceleratorCallback(),
media::cast::CreateDefaultVideoEncodeMemoryCallback()));
io_message_loop.Run(); // Wait for video initialization.
io_message_loop.PostTask(
FROM_HERE,
base::Bind(&media::cast::CastSender::InitializeAudio,
base::Unretained(cast_sender.get()),
audio_config,
base::Bind(&QuitLoopOnInitializationResult)));
io_message_loop.Run(); // Wait for audio initialization.
fake_media_source->Start(cast_sender->audio_frame_input(),
cast_sender->video_frame_input());
io_message_loop.Run();
return 0;
}