// 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 #include #include #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 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::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 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& cast_environment, scoped_ptr video_event_subscriber, scoped_ptr 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& cast_environment, scoped_ptr video_stats_subscriber, scoped_ptr audio_stats_subscriber, scoped_ptr estimator) { cast_environment->logger()->Unsubscribe(video_stats_subscriber.get()); cast_environment->logger()->Unsubscribe(audio_stats_subscriber.get()); cast_environment->logger()->Unsubscribe(estimator.get()); scoped_ptr 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; } } // 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(remote_port)); // Enable raw event and stats logging. // Running transport on the main thread. scoped_refptr cast_environment( new media::cast::CastEnvironment( make_scoped_ptr(new base::DefaultTickClock()), io_message_loop.task_runner(), audio_thread.task_runner(), video_thread.task_runner())); // SendProcess initialization. scoped_ptr 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); // CastTransportSender initialization. scoped_ptr transport_sender = media::cast::CastTransportSender::Create( nullptr, // net log. cast_environment->Clock(), net::IPEndPoint(), remote_endpoint, make_scoped_ptr(new base::DictionaryValue), // options base::Bind(&UpdateCastTransportStatus), base::Bind(&media::cast::LogEventDispatcher::DispatchBatchOfEvents, base::Unretained(cast_environment->logger())), base::TimeDelta::FromSeconds(1), media::cast::PacketReceiverCallback(), io_message_loop.task_runner()); // Set up event subscribers. scoped_ptr video_event_subscriber; scoped_ptr 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. scoped_ptr offset_estimator( new media::cast::ReceiverTimeOffsetEstimatorImpl()); cast_environment->logger()->Subscribe(offset_estimator.get()); scoped_ptr video_stats_subscriber( new media::cast::StatsEventSubscriber(media::cast::VIDEO_EVENT, cast_environment->Clock(), offset_estimator.get())); scoped_ptr 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. scoped_ptr 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; }