1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // Test application that simulates a cast sender - Data can be either generated
6 // or read from a file.
10 #include "base/at_exit.h"
11 #include "base/base_paths.h"
12 #include "base/command_line.h"
13 #include "base/files/file_path.h"
14 #include "base/json/json_writer.h"
15 #include "base/logging.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/path_service.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/threading/thread.h"
20 #include "base/time/default_tick_clock.h"
21 #include "base/values.h"
22 #include "media/base/media.h"
23 #include "media/base/video_frame.h"
24 #include "media/cast/cast_config.h"
25 #include "media/cast/cast_environment.h"
26 #include "media/cast/cast_sender.h"
27 #include "media/cast/logging/encoding_event_subscriber.h"
28 #include "media/cast/logging/log_serializer.h"
29 #include "media/cast/logging/logging_defines.h"
30 #include "media/cast/logging/proto/raw_events.pb.h"
31 #include "media/cast/logging/receiver_time_offset_estimator_impl.h"
32 #include "media/cast/logging/stats_event_subscriber.h"
33 #include "media/cast/net/cast_transport_defines.h"
34 #include "media/cast/net/cast_transport_sender.h"
35 #include "media/cast/net/udp_transport.h"
36 #include "media/cast/test/fake_media_source.h"
37 #include "media/cast/test/utility/default_config.h"
38 #include "media/cast/test/utility/input_builder.h"
42 // The max allowed size of serialized log.
43 const int kMaxSerializedLogBytes
= 10 * 1000 * 1000;
45 // Flags for this program:
47 // --address=xx.xx.xx.xx
48 // IP address of receiver.
51 // Port number of receiver.
53 // --source-file=xxx.webm
54 // WebM file as source of video frames.
57 // Override framerate of the video stream.
60 // Randomly vary the video frame sizes at random points in time. Has no
61 // effect if --source-file is being used.
62 const char kSwitchAddress
[] = "address";
63 const char kSwitchPort
[] = "port";
64 const char kSwitchSourceFile
[] = "source-file";
65 const char kSwitchFps
[] = "fps";
66 const char kSwitchVaryFrameSizes
[] = "vary-frame-sizes";
68 void UpdateCastTransportStatus(
69 media::cast::CastTransportStatus status
) {
70 VLOG(1) << "Transport status: " << status
;
74 const scoped_refptr
<media::cast::CastEnvironment
>& cast_environment
,
75 const std::vector
<media::cast::PacketEvent
>& packet_events
,
76 const std::vector
<media::cast::FrameEvent
>& frame_events
) {
77 VLOG(1) << "Got packet events from transport, size: " << packet_events
.size();
78 for (std::vector
<media::cast::PacketEvent
>::const_iterator it
=
79 packet_events
.begin();
80 it
!= packet_events
.end();
82 cast_environment
->Logging()->InsertPacketEvent(it
->timestamp
,
91 VLOG(1) << "Got frame events from transport, size: " << frame_events
.size();
92 for (std::vector
<media::cast::FrameEvent
>::const_iterator it
=
94 it
!= frame_events
.end();
96 cast_environment
->Logging()->InsertFrameEvent(it
->timestamp
,
104 void QuitLoopOnInitializationResult(media::cast::OperationalStatus result
) {
105 CHECK(result
== media::cast::STATUS_INITIALIZED
)
106 << "Cast sender uninitialized";
107 base::MessageLoop::current()->Quit();
110 net::IPEndPoint
CreateUDPAddress(std::string ip_str
, uint16 port
) {
111 net::IPAddressNumber ip_number
;
112 CHECK(net::ParseIPLiteralToNumber(ip_str
, &ip_number
));
113 return net::IPEndPoint(ip_number
, port
);
116 void DumpLoggingData(const media::cast::proto::LogMetadata
& log_metadata
,
117 const media::cast::FrameEventList
& frame_events
,
118 const media::cast::PacketEventList
& packet_events
,
119 base::ScopedFILE log_file
) {
120 VLOG(0) << "Frame map size: " << frame_events
.size();
121 VLOG(0) << "Packet map size: " << packet_events
.size();
123 scoped_ptr
<char[]> event_log(new char[kMaxSerializedLogBytes
]);
125 if (!media::cast::SerializeEvents(log_metadata
,
129 kMaxSerializedLogBytes
,
132 VLOG(0) << "Failed to serialize events.";
136 VLOG(0) << "Events serialized length: " << event_log_bytes
;
138 int ret
= fwrite(event_log
.get(), 1, event_log_bytes
, log_file
.get());
139 if (ret
!= event_log_bytes
)
140 VLOG(0) << "Failed to write logs to file.";
143 void WriteLogsToFileAndDestroySubscribers(
144 const scoped_refptr
<media::cast::CastEnvironment
>& cast_environment
,
145 scoped_ptr
<media::cast::EncodingEventSubscriber
> video_event_subscriber
,
146 scoped_ptr
<media::cast::EncodingEventSubscriber
> audio_event_subscriber
,
147 base::ScopedFILE video_log_file
,
148 base::ScopedFILE audio_log_file
) {
149 cast_environment
->Logging()->RemoveRawEventSubscriber(
150 video_event_subscriber
.get());
151 cast_environment
->Logging()->RemoveRawEventSubscriber(
152 audio_event_subscriber
.get());
154 VLOG(0) << "Dumping logging data for video stream.";
155 media::cast::proto::LogMetadata log_metadata
;
156 media::cast::FrameEventList frame_events
;
157 media::cast::PacketEventList packet_events
;
158 video_event_subscriber
->GetEventsAndReset(
159 &log_metadata
, &frame_events
, &packet_events
);
161 DumpLoggingData(log_metadata
,
164 video_log_file
.Pass());
166 VLOG(0) << "Dumping logging data for audio stream.";
167 audio_event_subscriber
->GetEventsAndReset(
168 &log_metadata
, &frame_events
, &packet_events
);
170 DumpLoggingData(log_metadata
,
173 audio_log_file
.Pass());
176 void WriteStatsAndDestroySubscribers(
177 const scoped_refptr
<media::cast::CastEnvironment
>& cast_environment
,
178 scoped_ptr
<media::cast::StatsEventSubscriber
> video_event_subscriber
,
179 scoped_ptr
<media::cast::StatsEventSubscriber
> audio_event_subscriber
,
180 scoped_ptr
<media::cast::ReceiverTimeOffsetEstimatorImpl
> estimator
) {
181 cast_environment
->Logging()->RemoveRawEventSubscriber(
182 video_event_subscriber
.get());
183 cast_environment
->Logging()->RemoveRawEventSubscriber(
184 audio_event_subscriber
.get());
185 cast_environment
->Logging()->RemoveRawEventSubscriber(estimator
.get());
187 scoped_ptr
<base::DictionaryValue
> stats
= video_event_subscriber
->GetStats();
189 base::JSONWriter::WriteWithOptions(
190 stats
.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT
, &json
);
191 VLOG(0) << "Video stats: " << json
;
193 stats
= audio_event_subscriber
->GetStats();
195 base::JSONWriter::WriteWithOptions(
196 stats
.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT
, &json
);
197 VLOG(0) << "Audio stats: " << json
;
202 int main(int argc
, char** argv
) {
203 base::AtExitManager at_exit
;
204 base::CommandLine::Init(argc
, argv
);
205 InitLogging(logging::LoggingSettings());
207 // Load the media module for FFmpeg decoding.
209 PathService::Get(base::DIR_MODULE
, &path
);
210 if (!media::InitializeMediaLibrary(path
)) {
211 LOG(ERROR
) << "Could not initialize media library.";
215 base::Thread
test_thread("Cast sender test app thread");
216 base::Thread
audio_thread("Cast audio encoder thread");
217 base::Thread
video_thread("Cast video encoder thread");
219 audio_thread
.Start();
220 video_thread
.Start();
222 base::MessageLoopForIO io_message_loop
;
224 // Default parameters.
225 base::CommandLine
* cmd
= base::CommandLine::ForCurrentProcess();
226 std::string remote_ip_address
= cmd
->GetSwitchValueASCII(kSwitchAddress
);
227 if (remote_ip_address
.empty())
228 remote_ip_address
= "127.0.0.1";
230 if (!base::StringToInt(cmd
->GetSwitchValueASCII(kSwitchPort
), &remote_port
) ||
231 remote_port
< 0 || remote_port
> 65535) {
234 LOG(INFO
) << "Sending to " << remote_ip_address
<< ":" << remote_port
237 media::cast::AudioSenderConfig audio_config
=
238 media::cast::GetDefaultAudioSenderConfig();
239 media::cast::VideoSenderConfig video_config
=
240 media::cast::GetDefaultVideoSenderConfig();
242 // Running transport on the main thread.
243 // Setting up transport config.
244 net::IPEndPoint remote_endpoint
=
245 CreateUDPAddress(remote_ip_address
, static_cast<uint16
>(remote_port
));
247 // Enable raw event and stats logging.
248 // Running transport on the main thread.
249 scoped_refptr
<media::cast::CastEnvironment
> cast_environment(
250 new media::cast::CastEnvironment(
251 make_scoped_ptr
<base::TickClock
>(new base::DefaultTickClock()),
252 io_message_loop
.message_loop_proxy(),
253 audio_thread
.message_loop_proxy(),
254 video_thread
.message_loop_proxy()));
256 // SendProcess initialization.
257 scoped_ptr
<media::cast::FakeMediaSource
> fake_media_source(
258 new media::cast::FakeMediaSource(test_thread
.message_loop_proxy(),
259 cast_environment
->Clock(),
264 int override_fps
= 0;
265 if (!base::StringToInt(cmd
->GetSwitchValueASCII(kSwitchFps
),
269 base::FilePath source_path
= cmd
->GetSwitchValuePath(kSwitchSourceFile
);
270 if (!source_path
.empty()) {
271 LOG(INFO
) << "Source: " << source_path
.value();
272 fake_media_source
->SetSourceFile(source_path
, override_fps
);
274 if (cmd
->HasSwitch(kSwitchVaryFrameSizes
))
275 fake_media_source
->SetVariableFrameSizeMode(true);
277 // CastTransportSender initialization.
278 scoped_ptr
<media::cast::CastTransportSender
> transport_sender
=
279 media::cast::CastTransportSender::Create(
281 cast_environment
->Clock(),
284 make_scoped_ptr(new base::DictionaryValue
), // options
285 base::Bind(&UpdateCastTransportStatus
),
286 base::Bind(&LogRawEvents
, cast_environment
),
287 base::TimeDelta::FromSeconds(1),
288 media::cast::PacketReceiverCallback(),
289 io_message_loop
.message_loop_proxy());
291 // Set up event subscribers.
292 scoped_ptr
<media::cast::EncodingEventSubscriber
> video_event_subscriber
;
293 scoped_ptr
<media::cast::EncodingEventSubscriber
> audio_event_subscriber
;
294 std::string
video_log_file_name("/tmp/video_events.log.gz");
295 std::string
audio_log_file_name("/tmp/audio_events.log.gz");
296 LOG(INFO
) << "Logging audio events to: " << audio_log_file_name
;
297 LOG(INFO
) << "Logging video events to: " << video_log_file_name
;
298 video_event_subscriber
.reset(new media::cast::EncodingEventSubscriber(
299 media::cast::VIDEO_EVENT
, 10000));
300 audio_event_subscriber
.reset(new media::cast::EncodingEventSubscriber(
301 media::cast::AUDIO_EVENT
, 10000));
302 cast_environment
->Logging()->AddRawEventSubscriber(
303 video_event_subscriber
.get());
304 cast_environment
->Logging()->AddRawEventSubscriber(
305 audio_event_subscriber
.get());
307 // Subscribers for stats.
308 scoped_ptr
<media::cast::ReceiverTimeOffsetEstimatorImpl
> offset_estimator(
309 new media::cast::ReceiverTimeOffsetEstimatorImpl());
310 cast_environment
->Logging()->AddRawEventSubscriber(offset_estimator
.get());
311 scoped_ptr
<media::cast::StatsEventSubscriber
> video_stats_subscriber(
312 new media::cast::StatsEventSubscriber(media::cast::VIDEO_EVENT
,
313 cast_environment
->Clock(),
314 offset_estimator
.get()));
315 scoped_ptr
<media::cast::StatsEventSubscriber
> audio_stats_subscriber(
316 new media::cast::StatsEventSubscriber(media::cast::AUDIO_EVENT
,
317 cast_environment
->Clock(),
318 offset_estimator
.get()));
319 cast_environment
->Logging()->AddRawEventSubscriber(
320 video_stats_subscriber
.get());
321 cast_environment
->Logging()->AddRawEventSubscriber(
322 audio_stats_subscriber
.get());
324 base::ScopedFILE
video_log_file(fopen(video_log_file_name
.c_str(), "w"));
325 if (!video_log_file
) {
326 VLOG(1) << "Failed to open video log file for writing.";
330 base::ScopedFILE
audio_log_file(fopen(audio_log_file_name
.c_str(), "w"));
331 if (!audio_log_file
) {
332 VLOG(1) << "Failed to open audio log file for writing.";
336 const int logging_duration_seconds
= 10;
337 io_message_loop
.message_loop_proxy()->PostDelayedTask(
339 base::Bind(&WriteLogsToFileAndDestroySubscribers
,
341 base::Passed(&video_event_subscriber
),
342 base::Passed(&audio_event_subscriber
),
343 base::Passed(&video_log_file
),
344 base::Passed(&audio_log_file
)),
345 base::TimeDelta::FromSeconds(logging_duration_seconds
));
347 io_message_loop
.message_loop_proxy()->PostDelayedTask(
349 base::Bind(&WriteStatsAndDestroySubscribers
,
351 base::Passed(&video_stats_subscriber
),
352 base::Passed(&audio_stats_subscriber
),
353 base::Passed(&offset_estimator
)),
354 base::TimeDelta::FromSeconds(logging_duration_seconds
));
356 // CastSender initialization.
357 scoped_ptr
<media::cast::CastSender
> cast_sender
=
358 media::cast::CastSender::Create(cast_environment
, transport_sender
.get());
359 io_message_loop
.PostTask(
361 base::Bind(&media::cast::CastSender::InitializeVideo
,
362 base::Unretained(cast_sender
.get()),
363 fake_media_source
->get_video_config(),
364 base::Bind(&QuitLoopOnInitializationResult
),
365 media::cast::CreateDefaultVideoEncodeAcceleratorCallback(),
366 media::cast::CreateDefaultVideoEncodeMemoryCallback()));
367 io_message_loop
.Run(); // Wait for video initialization.
368 io_message_loop
.PostTask(
370 base::Bind(&media::cast::CastSender::InitializeAudio
,
371 base::Unretained(cast_sender
.get()),
373 base::Bind(&QuitLoopOnInitializationResult
)));
374 io_message_loop
.Run(); // Wait for audio initialization.
376 fake_media_source
->Start(cast_sender
->audio_frame_input(),
377 cast_sender
->video_frame_input());
378 io_message_loop
.Run();