| // Copyright 2013 The Chromium Authors |
| // 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 <stdint.h> |
| |
| #include <memory> |
| #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/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/task/single_thread_task_executor.h" |
| #include "base/task/single_thread_task_runner.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/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_impl.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 { |
| |
| // 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::RunLoop::QuitCurrentWhenIdleDeprecated(); |
| } |
| |
| 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 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()); |
| } |
| |
| 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()); |
| |
| base::Value::Dict 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) {} |
| |
| TransportClient(const TransportClient&) = delete; |
| TransportClient& operator=(const TransportClient&) = delete; |
| |
| 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: |
| const raw_ptr<media::cast::LogEventDispatcher> |
| log_event_dispatcher_; // Not owned by this class. |
| }; |
| |
| } // 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::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO); |
| |
| // 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::DefaultTickClock::GetInstance(), io_task_executor.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::Seconds(1), |
| std::make_unique<TransportClient>(cast_environment->logger()), |
| std::make_unique<media::cast::UdpTransportImpl>( |
| io_task_executor.task_runner(), net::IPEndPoint(), |
| remote_endpoint, base::BindRepeating(&UpdateCastTransportStatus)), |
| io_task_executor.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 = |
| std::make_unique<media::cast::EncodingEventSubscriber>( |
| media::cast::VIDEO_EVENT, 10000); |
| audio_event_subscriber = |
| std::make_unique<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_task_executor.task_runner()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&WriteLogsToFileAndDestroySubscribers, cast_environment, |
| std::move(video_event_subscriber), |
| std::move(audio_event_subscriber), |
| std::move(video_log_file), std::move(audio_log_file)), |
| base::Seconds(logging_duration_seconds)); |
| |
| io_task_executor.task_runner()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&WriteStatsAndDestroySubscribers, cast_environment, |
| std::move(video_stats_subscriber), |
| std::move(audio_stats_subscriber), |
| std::move(offset_estimator)), |
| base::Seconds(logging_duration_seconds)); |
| |
| // CastSender initialization. |
| std::unique_ptr<media::cast::CastSender> cast_sender = |
| media::cast::CastSender::Create(cast_environment, transport_sender.get()); |
| io_task_executor.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&media::cast::CastSender::InitializeVideo, |
| base::Unretained(cast_sender.get()), |
| fake_media_source->get_video_config(), |
| base::BindRepeating(&QuitLoopOnInitializationResult), |
| base::DoNothing())); |
| base::RunLoop().Run(); // Wait for video initialization. |
| io_task_executor.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&media::cast::CastSender::InitializeAudio, |
| base::Unretained(cast_sender.get()), audio_config, |
| base::BindRepeating(&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; |
| } |