| // 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; |
| } |