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
, base::JSONWriter::OPTIONS_PRETTY_PRINT
, &json
);
191 VLOG(0) << "Video stats: " << json
;
193 stats
= audio_event_subscriber
->GetStats();
195 base::JSONWriter::WriteWithOptions(
196 *stats
, 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 // Prepare media module for FFmpeg decoding.
208 media::InitializeMediaLibrary();
210 base::Thread
test_thread("Cast sender test app thread");
211 base::Thread
audio_thread("Cast audio encoder thread");
212 base::Thread
video_thread("Cast video encoder thread");
214 audio_thread
.Start();
215 video_thread
.Start();
217 base::MessageLoopForIO io_message_loop
;
219 // Default parameters.
220 base::CommandLine
* cmd
= base::CommandLine::ForCurrentProcess();
221 std::string remote_ip_address
= cmd
->GetSwitchValueASCII(kSwitchAddress
);
222 if (remote_ip_address
.empty())
223 remote_ip_address
= "127.0.0.1";
225 if (!base::StringToInt(cmd
->GetSwitchValueASCII(kSwitchPort
), &remote_port
) ||
226 remote_port
< 0 || remote_port
> 65535) {
229 LOG(INFO
) << "Sending to " << remote_ip_address
<< ":" << remote_port
232 media::cast::AudioSenderConfig audio_config
=
233 media::cast::GetDefaultAudioSenderConfig();
234 media::cast::VideoSenderConfig video_config
=
235 media::cast::GetDefaultVideoSenderConfig();
237 // Running transport on the main thread.
238 // Setting up transport config.
239 net::IPEndPoint remote_endpoint
=
240 CreateUDPAddress(remote_ip_address
, static_cast<uint16
>(remote_port
));
242 // Enable raw event and stats logging.
243 // Running transport on the main thread.
244 scoped_refptr
<media::cast::CastEnvironment
> cast_environment(
245 new media::cast::CastEnvironment(
246 make_scoped_ptr
<base::TickClock
>(new base::DefaultTickClock()),
247 io_message_loop
.task_runner(),
248 audio_thread
.task_runner(),
249 video_thread
.task_runner()));
251 // SendProcess initialization.
252 scoped_ptr
<media::cast::FakeMediaSource
> fake_media_source(
253 new media::cast::FakeMediaSource(test_thread
.task_runner(),
254 cast_environment
->Clock(),
260 if (!base::StringToInt(cmd
->GetSwitchValueASCII(kSwitchFps
),
264 base::FilePath source_path
= cmd
->GetSwitchValuePath(kSwitchSourceFile
);
265 if (!source_path
.empty()) {
266 LOG(INFO
) << "Source: " << source_path
.value();
267 fake_media_source
->SetSourceFile(source_path
, final_fps
);
269 if (cmd
->HasSwitch(kSwitchVaryFrameSizes
))
270 fake_media_source
->SetVariableFrameSizeMode(true);
272 // CastTransportSender initialization.
273 scoped_ptr
<media::cast::CastTransportSender
> transport_sender
=
274 media::cast::CastTransportSender::Create(
276 cast_environment
->Clock(),
279 make_scoped_ptr(new base::DictionaryValue
), // options
280 base::Bind(&UpdateCastTransportStatus
),
281 base::Bind(&LogRawEvents
, cast_environment
),
282 base::TimeDelta::FromSeconds(1),
283 media::cast::PacketReceiverCallback(),
284 io_message_loop
.task_runner());
286 // Set up event subscribers.
287 scoped_ptr
<media::cast::EncodingEventSubscriber
> video_event_subscriber
;
288 scoped_ptr
<media::cast::EncodingEventSubscriber
> audio_event_subscriber
;
289 std::string
video_log_file_name("/tmp/video_events.log.gz");
290 std::string
audio_log_file_name("/tmp/audio_events.log.gz");
291 LOG(INFO
) << "Logging audio events to: " << audio_log_file_name
;
292 LOG(INFO
) << "Logging video events to: " << video_log_file_name
;
293 video_event_subscriber
.reset(new media::cast::EncodingEventSubscriber(
294 media::cast::VIDEO_EVENT
, 10000));
295 audio_event_subscriber
.reset(new media::cast::EncodingEventSubscriber(
296 media::cast::AUDIO_EVENT
, 10000));
297 cast_environment
->Logging()->AddRawEventSubscriber(
298 video_event_subscriber
.get());
299 cast_environment
->Logging()->AddRawEventSubscriber(
300 audio_event_subscriber
.get());
302 // Subscribers for stats.
303 scoped_ptr
<media::cast::ReceiverTimeOffsetEstimatorImpl
> offset_estimator(
304 new media::cast::ReceiverTimeOffsetEstimatorImpl());
305 cast_environment
->Logging()->AddRawEventSubscriber(offset_estimator
.get());
306 scoped_ptr
<media::cast::StatsEventSubscriber
> video_stats_subscriber(
307 new media::cast::StatsEventSubscriber(media::cast::VIDEO_EVENT
,
308 cast_environment
->Clock(),
309 offset_estimator
.get()));
310 scoped_ptr
<media::cast::StatsEventSubscriber
> audio_stats_subscriber(
311 new media::cast::StatsEventSubscriber(media::cast::AUDIO_EVENT
,
312 cast_environment
->Clock(),
313 offset_estimator
.get()));
314 cast_environment
->Logging()->AddRawEventSubscriber(
315 video_stats_subscriber
.get());
316 cast_environment
->Logging()->AddRawEventSubscriber(
317 audio_stats_subscriber
.get());
319 base::ScopedFILE
video_log_file(fopen(video_log_file_name
.c_str(), "w"));
320 if (!video_log_file
) {
321 VLOG(1) << "Failed to open video log file for writing.";
325 base::ScopedFILE
audio_log_file(fopen(audio_log_file_name
.c_str(), "w"));
326 if (!audio_log_file
) {
327 VLOG(1) << "Failed to open audio log file for writing.";
331 const int logging_duration_seconds
= 10;
332 io_message_loop
.task_runner()->PostDelayedTask(
334 base::Bind(&WriteLogsToFileAndDestroySubscribers
,
336 base::Passed(&video_event_subscriber
),
337 base::Passed(&audio_event_subscriber
),
338 base::Passed(&video_log_file
),
339 base::Passed(&audio_log_file
)),
340 base::TimeDelta::FromSeconds(logging_duration_seconds
));
342 io_message_loop
.task_runner()->PostDelayedTask(
344 base::Bind(&WriteStatsAndDestroySubscribers
,
346 base::Passed(&video_stats_subscriber
),
347 base::Passed(&audio_stats_subscriber
),
348 base::Passed(&offset_estimator
)),
349 base::TimeDelta::FromSeconds(logging_duration_seconds
));
351 // CastSender initialization.
352 scoped_ptr
<media::cast::CastSender
> cast_sender
=
353 media::cast::CastSender::Create(cast_environment
, transport_sender
.get());
354 io_message_loop
.PostTask(
356 base::Bind(&media::cast::CastSender::InitializeVideo
,
357 base::Unretained(cast_sender
.get()),
358 fake_media_source
->get_video_config(),
359 base::Bind(&QuitLoopOnInitializationResult
),
360 media::cast::CreateDefaultVideoEncodeAcceleratorCallback(),
361 media::cast::CreateDefaultVideoEncodeMemoryCallback()));
362 io_message_loop
.Run(); // Wait for video initialization.
363 io_message_loop
.PostTask(
365 base::Bind(&media::cast::CastSender::InitializeAudio
,
366 base::Unretained(cast_sender
.get()),
368 base::Bind(&QuitLoopOnInitializationResult
)));
369 io_message_loop
.Run(); // Wait for audio initialization.
371 fake_media_source
->Start(cast_sender
->audio_frame_input(),
372 cast_sender
->video_frame_input());
373 io_message_loop
.Run();