blob: 0b2bf9e25db38e40b1ad5f1ed7f5c9dd76893df6 [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 <memory>
#include <queue>
#include <utility>
#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/ptr_util.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.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.h"
#include "media/cast/net/cast_transport_defines.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"
#include "starboard/types.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 QuitLoopOnInitializationResult(media::cast::OperationalStatus result) {
CHECK(result == media::cast::STATUS_INITIALIZED)
<< "Cast sender uninitialized";
base::MessageLoop::current()->QuitWhenIdle();
}
net::IPEndPoint CreateUDPAddress(const std::string& ip_str, uint16_t port) {
net::IPAddress ip_address;
CHECK(ip_address.AssignFromIPLiteral(ip_str));
return net::IPEndPoint(ip_address, 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();
std::unique_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,
std::unique_ptr<media::cast::EncodingEventSubscriber>
video_event_subscriber,
std::unique_ptr<media::cast::EncodingEventSubscriber>
audio_event_subscriber,
base::ScopedFILE video_log_file, base::ScopedFILE audio_log_file) {
cast_environment->logger()->Unsubscribe(video_event_subscriber.get());
cast_environment->logger()->Unsubscribe(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,
std::move(video_log_file));
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,
std::move(audio_log_file));
}
void WriteStatsAndDestroySubscribers(
const scoped_refptr<media::cast::CastEnvironment>& cast_environment,
std::unique_ptr<media::cast::StatsEventSubscriber> video_stats_subscriber,
std::unique_ptr<media::cast::StatsEventSubscriber> audio_stats_subscriber,
std::unique_ptr<media::cast::ReceiverTimeOffsetEstimatorImpl> estimator) {
cast_environment->logger()->Unsubscribe(video_stats_subscriber.get());
cast_environment->logger()->Unsubscribe(audio_stats_subscriber.get());
cast_environment->logger()->Unsubscribe(estimator.get());
std::unique_ptr<base::DictionaryValue> stats =
video_stats_subscriber->GetStats();
std::string json;
base::JSONWriter::WriteWithOptions(
*stats, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
VLOG(0) << "Video stats: " << json;
stats = audio_stats_subscriber->GetStats();
json.clear();
base::JSONWriter::WriteWithOptions(
*stats, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
VLOG(0) << "Audio stats: " << json;
}
class TransportClient : public media::cast::CastTransport::Client {
public:
explicit TransportClient(
media::cast::LogEventDispatcher* log_event_dispatcher)
: log_event_dispatcher_(log_event_dispatcher) {}
void OnStatusChanged(media::cast::CastTransportStatus status) final {
VLOG(1) << "Transport status: " << status;
};
void OnLoggingEventsReceived(
std::unique_ptr<std::vector<media::cast::FrameEvent>> frame_events,
std::unique_ptr<std::vector<media::cast::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<media::cast::Packet> packet) final {}
private:
media::cast::LogEventDispatcher* const
log_event_dispatcher_; // Not owned by this class.
DISALLOW_COPY_AND_ASSIGN(TransportClient);
};
} // 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::FrameSenderConfig audio_config =
media::cast::GetDefaultAudioSenderConfig();
media::cast::FrameSenderConfig 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_t>(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(
base::WrapUnique<base::TickClock>(new base::DefaultTickClock()),
io_message_loop.task_runner(), audio_thread.task_runner(),
video_thread.task_runner()));
// SendProcess initialization.
std::unique_ptr<media::cast::FakeMediaSource> fake_media_source(
new media::cast::FakeMediaSource(test_thread.task_runner(),
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);
// CastTransport initialization.
std::unique_ptr<media::cast::CastTransport> transport_sender =
media::cast::CastTransport::Create(
cast_environment->Clock(), base::TimeDelta::FromSeconds(1),
base::MakeUnique<TransportClient>(cast_environment->logger()),
base::MakeUnique<media::cast::UdpTransport>(
nullptr, io_message_loop.task_runner(), net::IPEndPoint(),
remote_endpoint, base::Bind(&UpdateCastTransportStatus)),
io_message_loop.task_runner());
// Set up event subscribers.
std::unique_ptr<media::cast::EncodingEventSubscriber> video_event_subscriber;
std::unique_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->logger()->Subscribe(video_event_subscriber.get());
cast_environment->logger()->Subscribe(audio_event_subscriber.get());
// Subscribers for stats.
std::unique_ptr<media::cast::ReceiverTimeOffsetEstimatorImpl>
offset_estimator(new media::cast::ReceiverTimeOffsetEstimatorImpl());
cast_environment->logger()->Subscribe(offset_estimator.get());
std::unique_ptr<media::cast::StatsEventSubscriber> video_stats_subscriber(
new media::cast::StatsEventSubscriber(media::cast::VIDEO_EVENT,
cast_environment->Clock(),
offset_estimator.get()));
std::unique_ptr<media::cast::StatsEventSubscriber> audio_stats_subscriber(
new media::cast::StatsEventSubscriber(media::cast::AUDIO_EVENT,
cast_environment->Clock(),
offset_estimator.get()));
cast_environment->logger()->Subscribe(video_stats_subscriber.get());
cast_environment->logger()->Subscribe(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.task_runner()->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.task_runner()->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.
std::unique_ptr<media::cast::CastSender> cast_sender =
media::cast::CastSender::Create(cast_environment, transport_sender.get());
io_message_loop.task_runner()->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()));
base::RunLoop().Run(); // Wait for video initialization.
io_message_loop.task_runner()->PostTask(
FROM_HERE, base::Bind(&media::cast::CastSender::InitializeAudio,
base::Unretained(cast_sender.get()), audio_config,
base::Bind(&QuitLoopOnInitializationResult)));
base::RunLoop().Run(); // Wait for audio initialization.
fake_media_source->Start(cast_sender->audio_frame_input(),
cast_sender->video_frame_input());
base::RunLoop().Run();
return 0;
}