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"
41 static const int kAudioChannels
= 2;
42 static const int kAudioSamplingFrequency
= 48000;
44 // The max allowed size of serialized log.
45 const int kMaxSerializedLogBytes
= 10 * 1000 * 1000;
47 // Flags for this program:
49 // --address=xx.xx.xx.xx
50 // IP address of receiver.
53 // Port number of receiver.
55 // --source-file=xxx.webm
56 // WebM file as source of video frames.
59 // Override framerate of the video stream.
60 const char kSwitchAddress
[] = "address";
61 const char kSwitchPort
[] = "port";
62 const char kSwitchSourceFile
[] = "source-file";
63 const char kSwitchFps
[] = "fps";
65 media::cast::AudioSenderConfig
GetAudioSenderConfig() {
66 media::cast::AudioSenderConfig audio_config
;
68 audio_config
.use_external_encoder
= false;
69 audio_config
.frequency
= kAudioSamplingFrequency
;
70 audio_config
.channels
= kAudioChannels
;
71 audio_config
.bitrate
= 0; // Use Opus auto-VBR mode.
72 audio_config
.codec
= media::cast::CODEC_AUDIO_OPUS
;
73 audio_config
.ssrc
= 1;
74 audio_config
.incoming_feedback_ssrc
= 2;
75 audio_config
.rtp_payload_type
= 127;
76 // TODO(miu): The default in cast_defines.h is 100. Should this be 100, and
77 // should receiver.cc's config also be 100?
78 audio_config
.max_playout_delay
= base::TimeDelta::FromMilliseconds(300);
82 media::cast::VideoSenderConfig
GetVideoSenderConfig() {
83 media::cast::VideoSenderConfig video_config
;
85 video_config
.use_external_encoder
= false;
88 video_config
.width
= 1280;
89 video_config
.height
= 720;
90 video_config
.max_frame_rate
= 30;
93 video_config
.max_bitrate
= 2500000;
94 video_config
.min_bitrate
= 100000;
95 video_config
.start_bitrate
= video_config
.min_bitrate
;
98 video_config
.codec
= media::cast::CODEC_VIDEO_VP8
;
99 video_config
.max_number_of_video_buffers_used
= 1;
100 video_config
.number_of_encode_threads
= 2;
103 video_config
.min_qp
= 4;
104 video_config
.max_qp
= 40;
106 // SSRCs and payload type. Don't change them.
107 video_config
.ssrc
= 11;
108 video_config
.incoming_feedback_ssrc
= 12;
109 video_config
.rtp_payload_type
= 96;
110 // TODO(miu): The default in cast_defines.h is 100. Should this be 100, and
111 // should receiver.cc's config also be 100?
112 video_config
.max_playout_delay
= base::TimeDelta::FromMilliseconds(300);
116 void UpdateCastTransportStatus(
117 media::cast::CastTransportStatus status
) {
118 VLOG(1) << "Transport status: " << status
;
122 const scoped_refptr
<media::cast::CastEnvironment
>& cast_environment
,
123 const std::vector
<media::cast::PacketEvent
>& packet_events
,
124 const std::vector
<media::cast::FrameEvent
>& frame_events
) {
125 VLOG(1) << "Got packet events from transport, size: " << packet_events
.size();
126 for (std::vector
<media::cast::PacketEvent
>::const_iterator it
=
127 packet_events
.begin();
128 it
!= packet_events
.end();
130 cast_environment
->Logging()->InsertPacketEvent(it
->timestamp
,
139 VLOG(1) << "Got frame events from transport, size: " << frame_events
.size();
140 for (std::vector
<media::cast::FrameEvent
>::const_iterator it
=
141 frame_events
.begin();
142 it
!= frame_events
.end();
144 cast_environment
->Logging()->InsertFrameEvent(it
->timestamp
,
152 void InitializationResult(media::cast::CastInitializationStatus result
) {
153 bool end_result
= result
== media::cast::STATUS_AUDIO_INITIALIZED
||
154 result
== media::cast::STATUS_VIDEO_INITIALIZED
;
155 CHECK(end_result
) << "Cast sender uninitialized";
158 net::IPEndPoint
CreateUDPAddress(std::string ip_str
, int port
) {
159 net::IPAddressNumber ip_number
;
160 CHECK(net::ParseIPLiteralToNumber(ip_str
, &ip_number
));
161 return net::IPEndPoint(ip_number
, port
);
164 void DumpLoggingData(const media::cast::proto::LogMetadata
& log_metadata
,
165 const media::cast::FrameEventList
& frame_events
,
166 const media::cast::PacketEventList
& packet_events
,
167 base::ScopedFILE log_file
) {
168 VLOG(0) << "Frame map size: " << frame_events
.size();
169 VLOG(0) << "Packet map size: " << packet_events
.size();
171 scoped_ptr
<char[]> event_log(new char[kMaxSerializedLogBytes
]);
173 if (!media::cast::SerializeEvents(log_metadata
,
177 kMaxSerializedLogBytes
,
180 VLOG(0) << "Failed to serialize events.";
184 VLOG(0) << "Events serialized length: " << event_log_bytes
;
186 int ret
= fwrite(event_log
.get(), 1, event_log_bytes
, log_file
.get());
187 if (ret
!= event_log_bytes
)
188 VLOG(0) << "Failed to write logs to file.";
191 void WriteLogsToFileAndDestroySubscribers(
192 const scoped_refptr
<media::cast::CastEnvironment
>& cast_environment
,
193 scoped_ptr
<media::cast::EncodingEventSubscriber
> video_event_subscriber
,
194 scoped_ptr
<media::cast::EncodingEventSubscriber
> audio_event_subscriber
,
195 base::ScopedFILE video_log_file
,
196 base::ScopedFILE audio_log_file
) {
197 cast_environment
->Logging()->RemoveRawEventSubscriber(
198 video_event_subscriber
.get());
199 cast_environment
->Logging()->RemoveRawEventSubscriber(
200 audio_event_subscriber
.get());
202 VLOG(0) << "Dumping logging data for video stream.";
203 media::cast::proto::LogMetadata log_metadata
;
204 media::cast::FrameEventList frame_events
;
205 media::cast::PacketEventList packet_events
;
206 video_event_subscriber
->GetEventsAndReset(
207 &log_metadata
, &frame_events
, &packet_events
);
209 DumpLoggingData(log_metadata
,
212 video_log_file
.Pass());
214 VLOG(0) << "Dumping logging data for audio stream.";
215 audio_event_subscriber
->GetEventsAndReset(
216 &log_metadata
, &frame_events
, &packet_events
);
218 DumpLoggingData(log_metadata
,
221 audio_log_file
.Pass());
224 void WriteStatsAndDestroySubscribers(
225 const scoped_refptr
<media::cast::CastEnvironment
>& cast_environment
,
226 scoped_ptr
<media::cast::StatsEventSubscriber
> video_event_subscriber
,
227 scoped_ptr
<media::cast::StatsEventSubscriber
> audio_event_subscriber
,
228 scoped_ptr
<media::cast::ReceiverTimeOffsetEstimatorImpl
> estimator
) {
229 cast_environment
->Logging()->RemoveRawEventSubscriber(
230 video_event_subscriber
.get());
231 cast_environment
->Logging()->RemoveRawEventSubscriber(
232 audio_event_subscriber
.get());
233 cast_environment
->Logging()->RemoveRawEventSubscriber(estimator
.get());
235 scoped_ptr
<base::DictionaryValue
> stats
= video_event_subscriber
->GetStats();
237 base::JSONWriter::WriteWithOptions(
238 stats
.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT
, &json
);
239 VLOG(0) << "Video stats: " << json
;
241 stats
= audio_event_subscriber
->GetStats();
243 base::JSONWriter::WriteWithOptions(
244 stats
.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT
, &json
);
245 VLOG(0) << "Audio stats: " << json
;
250 int main(int argc
, char** argv
) {
251 base::AtExitManager at_exit
;
252 CommandLine::Init(argc
, argv
);
253 InitLogging(logging::LoggingSettings());
255 // Load the media module for FFmpeg decoding.
257 PathService::Get(base::DIR_MODULE
, &path
);
258 if (!media::InitializeMediaLibrary(path
)) {
259 LOG(ERROR
) << "Could not initialize media library.";
263 base::Thread
test_thread("Cast sender test app thread");
264 base::Thread
audio_thread("Cast audio encoder thread");
265 base::Thread
video_thread("Cast video encoder thread");
267 audio_thread
.Start();
268 video_thread
.Start();
270 base::MessageLoopForIO io_message_loop
;
272 // Default parameters.
273 CommandLine
* cmd
= CommandLine::ForCurrentProcess();
274 std::string remote_ip_address
= cmd
->GetSwitchValueASCII(kSwitchAddress
);
275 if (remote_ip_address
.empty())
276 remote_ip_address
= "127.0.0.1";
278 if (!base::StringToInt(cmd
->GetSwitchValueASCII(kSwitchPort
),
282 LOG(INFO
) << "Sending to " << remote_ip_address
<< ":" << remote_port
285 media::cast::AudioSenderConfig audio_config
= GetAudioSenderConfig();
286 media::cast::VideoSenderConfig video_config
= GetVideoSenderConfig();
288 // Running transport on the main thread.
289 // Setting up transport config.
290 net::IPEndPoint remote_endpoint
=
291 CreateUDPAddress(remote_ip_address
, remote_port
);
293 // Enable raw event and stats logging.
294 // Running transport on the main thread.
295 scoped_refptr
<media::cast::CastEnvironment
> cast_environment(
296 new media::cast::CastEnvironment(
297 make_scoped_ptr
<base::TickClock
>(new base::DefaultTickClock()),
298 io_message_loop
.message_loop_proxy(),
299 audio_thread
.message_loop_proxy(),
300 video_thread
.message_loop_proxy()));
302 // SendProcess initialization.
303 scoped_ptr
<media::cast::FakeMediaSource
> fake_media_source(
304 new media::cast::FakeMediaSource(test_thread
.message_loop_proxy(),
305 cast_environment
->Clock(),
308 int override_fps
= 0;
309 if (!base::StringToInt(cmd
->GetSwitchValueASCII(kSwitchFps
),
313 base::FilePath source_path
= cmd
->GetSwitchValuePath(kSwitchSourceFile
);
314 if (!source_path
.empty()) {
315 LOG(INFO
) << "Source: " << source_path
.value();
316 fake_media_source
->SetSourceFile(source_path
, override_fps
);
319 // CastTransportSender initialization.
320 scoped_ptr
<media::cast::CastTransportSender
> transport_sender
=
321 media::cast::CastTransportSender::Create(
323 cast_environment
->Clock(),
325 make_scoped_ptr(new base::DictionaryValue
), // options
326 base::Bind(&UpdateCastTransportStatus
),
327 base::Bind(&LogRawEvents
, cast_environment
),
328 base::TimeDelta::FromSeconds(1),
329 io_message_loop
.message_loop_proxy());
331 // CastSender initialization.
332 scoped_ptr
<media::cast::CastSender
> cast_sender
=
333 media::cast::CastSender::Create(cast_environment
, transport_sender
.get());
334 cast_sender
->InitializeVideo(
335 fake_media_source
->get_video_config(),
336 base::Bind(&InitializationResult
),
337 media::cast::CreateDefaultVideoEncodeAcceleratorCallback(),
338 media::cast::CreateDefaultVideoEncodeMemoryCallback());
339 cast_sender
->InitializeAudio(audio_config
, base::Bind(&InitializationResult
));
341 // Set up event subscribers.
342 scoped_ptr
<media::cast::EncodingEventSubscriber
> video_event_subscriber
;
343 scoped_ptr
<media::cast::EncodingEventSubscriber
> audio_event_subscriber
;
344 std::string
video_log_file_name("/tmp/video_events.log.gz");
345 std::string
audio_log_file_name("/tmp/audio_events.log.gz");
346 LOG(INFO
) << "Logging audio events to: " << audio_log_file_name
;
347 LOG(INFO
) << "Logging video events to: " << video_log_file_name
;
348 video_event_subscriber
.reset(new media::cast::EncodingEventSubscriber(
349 media::cast::VIDEO_EVENT
, 10000));
350 audio_event_subscriber
.reset(new media::cast::EncodingEventSubscriber(
351 media::cast::AUDIO_EVENT
, 10000));
352 cast_environment
->Logging()->AddRawEventSubscriber(
353 video_event_subscriber
.get());
354 cast_environment
->Logging()->AddRawEventSubscriber(
355 audio_event_subscriber
.get());
357 // Subscribers for stats.
358 scoped_ptr
<media::cast::ReceiverTimeOffsetEstimatorImpl
> offset_estimator(
359 new media::cast::ReceiverTimeOffsetEstimatorImpl());
360 cast_environment
->Logging()->AddRawEventSubscriber(offset_estimator
.get());
361 scoped_ptr
<media::cast::StatsEventSubscriber
> video_stats_subscriber(
362 new media::cast::StatsEventSubscriber(media::cast::VIDEO_EVENT
,
363 cast_environment
->Clock(),
364 offset_estimator
.get()));
365 scoped_ptr
<media::cast::StatsEventSubscriber
> audio_stats_subscriber(
366 new media::cast::StatsEventSubscriber(media::cast::AUDIO_EVENT
,
367 cast_environment
->Clock(),
368 offset_estimator
.get()));
369 cast_environment
->Logging()->AddRawEventSubscriber(
370 video_stats_subscriber
.get());
371 cast_environment
->Logging()->AddRawEventSubscriber(
372 audio_stats_subscriber
.get());
374 base::ScopedFILE
video_log_file(fopen(video_log_file_name
.c_str(), "w"));
375 if (!video_log_file
) {
376 VLOG(1) << "Failed to open video log file for writing.";
380 base::ScopedFILE
audio_log_file(fopen(audio_log_file_name
.c_str(), "w"));
381 if (!audio_log_file
) {
382 VLOG(1) << "Failed to open audio log file for writing.";
386 const int logging_duration_seconds
= 10;
387 io_message_loop
.message_loop_proxy()->PostDelayedTask(
389 base::Bind(&WriteLogsToFileAndDestroySubscribers
,
391 base::Passed(&video_event_subscriber
),
392 base::Passed(&audio_event_subscriber
),
393 base::Passed(&video_log_file
),
394 base::Passed(&audio_log_file
)),
395 base::TimeDelta::FromSeconds(logging_duration_seconds
));
397 io_message_loop
.message_loop_proxy()->PostDelayedTask(
399 base::Bind(&WriteStatsAndDestroySubscribers
,
401 base::Passed(&video_stats_subscriber
),
402 base::Passed(&audio_stats_subscriber
),
403 base::Passed(&offset_estimator
)),
404 base::TimeDelta::FromSeconds(logging_duration_seconds
));
406 fake_media_source
->Start(cast_sender
->audio_frame_input(),
407 cast_sender
->video_frame_input());
409 io_message_loop
.Run();