sender.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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. 4 5// Test application that simulates a cast sender - Data can be either generated 6// or read from a file. 7 8#include "base/at_exit.h" 9#include "base/command_line.h" 10#include "base/file_util.h" 11#include "base/files/scoped_file.h" 12#include "base/logging.h" 13#include "base/memory/scoped_ptr.h" 14#include "base/threading/thread.h" 15#include "base/time/default_tick_clock.h" 16#include "media/base/video_frame.h" 17#include "media/cast/cast_config.h" 18#include "media/cast/cast_environment.h" 19#include "media/cast/cast_sender.h" 20#include "media/cast/logging/encoding_event_subscriber.h" 21#include "media/cast/logging/log_serializer.h" 22#include "media/cast/logging/logging_defines.h" 23#include "media/cast/logging/proto/raw_events.pb.h" 24#include "media/cast/test/utility/audio_utility.h" 25#include "media/cast/test/utility/default_config.h" 26#include "media/cast/test/utility/input_builder.h" 27#include "media/cast/test/utility/video_utility.h" 28#include "media/cast/transport/cast_transport_defines.h" 29#include "media/cast/transport/cast_transport_sender.h" 30#include "media/cast/transport/transport/udp_transport.h" 31#include "ui/gfx/size.h" 32 33namespace media { 34namespace cast { 35// Settings chosen to match default receiver settings. 36#define DEFAULT_RECEIVER_PORT "2344" 37#define DEFAULT_RECEIVER_IP "127.0.0.1" 38#define DEFAULT_READ_FROM_FILE "0" 39#define DEFAULT_AUDIO_SENDER_SSRC "1" 40#define DEFAULT_AUDIO_RECEIVER_SSRC "2" 41#define DEFAULT_AUDIO_PAYLOAD_TYPE "127" 42#define DEFAULT_VIDEO_SENDER_SSRC "11" 43#define DEFAULT_VIDEO_RECEIVER_SSRC "12" 44#define DEFAULT_VIDEO_PAYLOAD_TYPE "96" 45#define DEFAULT_VIDEO_CODEC_WIDTH "1280" 46#define DEFAULT_VIDEO_CODEC_HEIGHT "720" 47#define DEFAULT_VIDEO_CODEC_BITRATE "2000" 48#define DEFAULT_VIDEO_CODEC_MAX_BITRATE "4000" 49#define DEFAULT_VIDEO_CODEC_MIN_BITRATE "1000" 50 51#define DEFAULT_LOGGING_DURATION "10" 52#define DEFAULT_COMPRESS_LOGS "1" 53 54namespace { 55static const int kAudioChannels = 2; 56static const int kAudioSamplingFrequency = 48000; 57static const int kSoundFrequency = 1234; // Frequency of sinusoid wave. 58// The tests are commonly implemented with |kFrameTimerMs| RunTask function; 59// a normal video is 30 fps hence the 33 ms between frames. 60static const float kSoundVolume = 0.5f; 61static const int kFrameTimerMs = 33; 62 63// The max allowed size of serialized log. 64const int kMaxSerializedLogBytes = 10 * 1000 * 1000; 65} // namespace 66 67void GetPort(int* port) { 68 test::InputBuilder input( 69 "Enter receiver port.", DEFAULT_RECEIVER_PORT, 1, INT_MAX); 70 *port = input.GetIntInput(); 71} 72 73std::string GetIpAddress(const std::string display_text) { 74 test::InputBuilder input(display_text, DEFAULT_RECEIVER_IP, INT_MIN, INT_MAX); 75 std::string ip_address = input.GetStringInput(); 76 // Verify correct form: 77 while (std::count(ip_address.begin(), ip_address.end(), '.') != 3) { 78 ip_address = input.GetStringInput(); 79 } 80 return ip_address; 81} 82 83int GetLoggingDuration() { 84 test::InputBuilder input( 85 "Choose logging duration (seconds), 0 for no logging.", 86 DEFAULT_LOGGING_DURATION, 87 0, 88 INT_MAX); 89 return input.GetIntInput(); 90} 91 92std::string GetVideoLogFileDestination(bool compress) { 93 test::InputBuilder input( 94 "Enter video events log file destination.", 95 compress ? "./video_events.log.gz" : "./video_events.log", 96 INT_MIN, 97 INT_MAX); 98 return input.GetStringInput(); 99} 100 101std::string GetAudioLogFileDestination(bool compress) { 102 test::InputBuilder input( 103 "Enter audio events log file destination.", 104 compress ? "./audio_events.log.gz" : "./audio_events.log", 105 INT_MIN, 106 INT_MAX); 107 return input.GetStringInput(); 108} 109 110bool CompressLogs() { 111 test::InputBuilder input( 112 "Enter 1 to enable compression on logs.", DEFAULT_COMPRESS_LOGS, 0, 1); 113 return (1 == input.GetIntInput()); 114} 115 116bool ReadFromFile() { 117 test::InputBuilder input( 118 "Enter 1 to read from file.", DEFAULT_READ_FROM_FILE, 0, 1); 119 return (1 == input.GetIntInput()); 120} 121 122std::string GetVideoFile() { 123 test::InputBuilder input( 124 "Enter file and path to raw video file.", "", INT_MIN, INT_MAX); 125 return input.GetStringInput(); 126} 127 128void GetSsrcs(AudioSenderConfig* audio_config) { 129 test::InputBuilder input_tx( 130 "Choose audio sender SSRC.", DEFAULT_AUDIO_SENDER_SSRC, 1, INT_MAX); 131 audio_config->sender_ssrc = input_tx.GetIntInput(); 132 133 test::InputBuilder input_rx( 134 "Choose audio receiver SSRC.", DEFAULT_AUDIO_RECEIVER_SSRC, 1, INT_MAX); 135 audio_config->incoming_feedback_ssrc = input_rx.GetIntInput(); 136} 137 138void GetSsrcs(VideoSenderConfig* video_config) { 139 test::InputBuilder input_tx( 140 "Choose video sender SSRC.", DEFAULT_VIDEO_SENDER_SSRC, 1, INT_MAX); 141 video_config->sender_ssrc = input_tx.GetIntInput(); 142 143 test::InputBuilder input_rx( 144 "Choose video receiver SSRC.", DEFAULT_VIDEO_RECEIVER_SSRC, 1, INT_MAX); 145 video_config->incoming_feedback_ssrc = input_rx.GetIntInput(); 146} 147 148void GetPayloadtype(AudioSenderConfig* audio_config) { 149 test::InputBuilder input( 150 "Choose audio sender payload type.", DEFAULT_AUDIO_PAYLOAD_TYPE, 96, 127); 151 audio_config->rtp_config.payload_type = input.GetIntInput(); 152} 153 154AudioSenderConfig GetAudioSenderConfig() { 155 AudioSenderConfig audio_config; 156 157 GetSsrcs(&audio_config); 158 GetPayloadtype(&audio_config); 159 160 audio_config.rtcp_c_name = "audio_sender@a.b.c.d"; 161 162 VLOG(0) << "Using OPUS 48Khz stereo at 64kbit/s"; 163 audio_config.use_external_encoder = false; 164 audio_config.frequency = kAudioSamplingFrequency; 165 audio_config.channels = kAudioChannels; 166 audio_config.bitrate = 64000; 167 audio_config.codec = transport::kOpus; 168 return audio_config; 169} 170 171void GetPayloadtype(VideoSenderConfig* video_config) { 172 test::InputBuilder input( 173 "Choose video sender payload type.", DEFAULT_VIDEO_PAYLOAD_TYPE, 96, 127); 174 video_config->rtp_config.payload_type = input.GetIntInput(); 175} 176 177void GetVideoCodecSize(VideoSenderConfig* video_config) { 178 test::InputBuilder input_width( 179 "Choose video width.", DEFAULT_VIDEO_CODEC_WIDTH, 144, 1920); 180 video_config->width = input_width.GetIntInput(); 181 182 test::InputBuilder input_height( 183 "Choose video height.", DEFAULT_VIDEO_CODEC_HEIGHT, 176, 1080); 184 video_config->height = input_height.GetIntInput(); 185} 186 187void GetVideoBitrates(VideoSenderConfig* video_config) { 188 test::InputBuilder input_start_br( 189 "Choose start bitrate[kbps].", DEFAULT_VIDEO_CODEC_BITRATE, 0, INT_MAX); 190 video_config->start_bitrate = input_start_br.GetIntInput() * 1000; 191 192 test::InputBuilder input_max_br( 193 "Choose max bitrate[kbps].", DEFAULT_VIDEO_CODEC_MAX_BITRATE, 0, INT_MAX); 194 video_config->max_bitrate = input_max_br.GetIntInput() * 1000; 195 196 test::InputBuilder input_min_br( 197 "Choose min bitrate[kbps].", DEFAULT_VIDEO_CODEC_MIN_BITRATE, 0, INT_MAX); 198 video_config->min_bitrate = input_min_br.GetIntInput() * 1000; 199} 200 201VideoSenderConfig GetVideoSenderConfig() { 202 VideoSenderConfig video_config; 203 204 GetSsrcs(&video_config); 205 GetPayloadtype(&video_config); 206 GetVideoCodecSize(&video_config); 207 GetVideoBitrates(&video_config); 208 209 video_config.rtcp_c_name = "video_sender@a.b.c.d"; 210 211 video_config.use_external_encoder = false; 212 213 VLOG(0) << "Using VP8 at 30 fps"; 214 video_config.min_qp = 4; 215 video_config.max_qp = 40; 216 video_config.max_frame_rate = 30; 217 video_config.codec = transport::kVp8; 218 video_config.max_number_of_video_buffers_used = 1; 219 return video_config; 220} 221 222class SendProcess { 223 public: 224 SendProcess(scoped_refptr<base::SingleThreadTaskRunner> thread_proxy, 225 base::TickClock* clock, 226 const VideoSenderConfig& video_config, 227 scoped_refptr<AudioFrameInput> audio_frame_input, 228 scoped_refptr<VideoFrameInput> video_frame_input) 229 : test_app_thread_proxy_(thread_proxy), 230 video_config_(video_config), 231 audio_diff_(kFrameTimerMs), 232 audio_frame_input_(audio_frame_input), 233 video_frame_input_(video_frame_input), 234 synthetic_count_(0), 235 clock_(clock), 236 start_time_(), 237 send_time_(), 238 weak_factory_(this) { 239 audio_bus_factory_.reset(new TestAudioBusFactory(kAudioChannels, 240 kAudioSamplingFrequency, 241 kSoundFrequency, 242 kSoundVolume)); 243 if (ReadFromFile()) { 244 std::string video_file_name = GetVideoFile(); 245 video_file_ = fopen(video_file_name.c_str(), "r"); 246 if (video_file_ == NULL) { 247 VLOG(1) << "Failed to open file"; 248 exit(-1); 249 } 250 } else { 251 video_file_ = NULL; 252 } 253 } 254 255 ~SendProcess() { 256 if (video_file_) 257 fclose(video_file_); 258 } 259 260 void SendFrame() { 261 // Make sure that we don't drift. 262 int num_10ms_blocks = audio_diff_ / 10; 263 // Avoid drift. 264 audio_diff_ += kFrameTimerMs - num_10ms_blocks * 10; 265 266 audio_frame_input_->InsertAudio( 267 audio_bus_factory_->NextAudioBus( 268 base::TimeDelta::FromMilliseconds(10) * num_10ms_blocks), 269 clock_->NowTicks()); 270 271 gfx::Size size(video_config_.width, video_config_.height); 272 // TODO(mikhal): Use the provided timestamp. 273 if (start_time_.is_null()) 274 start_time_ = clock_->NowTicks(); 275 base::TimeDelta time_diff = clock_->NowTicks() - start_time_; 276 scoped_refptr<media::VideoFrame> video_frame = 277 media::VideoFrame::CreateFrame( 278 VideoFrame::I420, size, gfx::Rect(size), size, time_diff); 279 if (video_file_) { 280 if (!PopulateVideoFrameFromFile(video_frame, video_file_)) 281 return; 282 } else { 283 PopulateVideoFrame(video_frame, synthetic_count_); 284 ++synthetic_count_; 285 } 286 287 // Time the sending of the frame to match the set frame rate. 288 // Sleep if that time has yet to elapse. 289 base::TimeTicks now = clock_->NowTicks(); 290 base::TimeDelta video_frame_time = 291 base::TimeDelta::FromMilliseconds(kFrameTimerMs); 292 base::TimeDelta elapsed_time = now - send_time_; 293 if (elapsed_time < video_frame_time) { 294 VLOG(1) << "Wait" << (video_frame_time - elapsed_time).InMilliseconds(); 295 test_app_thread_proxy_->PostDelayedTask( 296 FROM_HERE, 297 base::Bind(&SendProcess::SendVideoFrameOnTime, 298 weak_factory_.GetWeakPtr(), 299 video_frame), 300 video_frame_time - elapsed_time); 301 } else { 302 test_app_thread_proxy_->PostTask( 303 FROM_HERE, 304 base::Bind(&SendProcess::SendVideoFrameOnTime, 305 weak_factory_.GetWeakPtr(), 306 video_frame)); 307 } 308 } 309 310 void SendVideoFrameOnTime(scoped_refptr<media::VideoFrame> video_frame) { 311 send_time_ = clock_->NowTicks(); 312 video_frame_input_->InsertRawVideoFrame(video_frame, send_time_); 313 test_app_thread_proxy_->PostTask( 314 FROM_HERE, base::Bind(&SendProcess::SendFrame, base::Unretained(this))); 315 } 316 317 private: 318 scoped_refptr<base::SingleThreadTaskRunner> test_app_thread_proxy_; 319 const VideoSenderConfig video_config_; 320 int audio_diff_; 321 const scoped_refptr<AudioFrameInput> audio_frame_input_; 322 const scoped_refptr<VideoFrameInput> video_frame_input_; 323 FILE* video_file_; 324 uint8 synthetic_count_; 325 base::TickClock* const clock_; // Not owned by this class. 326 base::TimeTicks start_time_; 327 base::TimeTicks send_time_; 328 scoped_ptr<TestAudioBusFactory> audio_bus_factory_; 329 330 // NOTE: Weak pointers must be invalidated before all other member variables. 331 base::WeakPtrFactory<SendProcess> weak_factory_; 332 333 DISALLOW_COPY_AND_ASSIGN(SendProcess); 334}; 335 336} // namespace cast 337} // namespace media 338 339namespace { 340void UpdateCastTransportStatus( 341 media::cast::transport::CastTransportStatus status) {} 342 343void LogRawEvents( 344 const scoped_refptr<media::cast::CastEnvironment>& cast_environment, 345 const std::vector<media::cast::PacketEvent>& packet_events) { 346 VLOG(1) << "Got packet events from transport, size: " << packet_events.size(); 347 for (std::vector<media::cast::PacketEvent>::const_iterator it = 348 packet_events.begin(); 349 it != packet_events.end(); 350 ++it) { 351 cast_environment->Logging()->InsertPacketEvent(it->timestamp, 352 it->type, 353 it->rtp_timestamp, 354 it->frame_id, 355 it->packet_id, 356 it->max_packet_id, 357 it->size); 358 } 359} 360 361void InitializationResult(media::cast::CastInitializationStatus result) { 362 bool end_result = result == media::cast::STATUS_AUDIO_INITIALIZED || 363 result == media::cast::STATUS_VIDEO_INITIALIZED; 364 CHECK(end_result) << "Cast sender uninitialized"; 365} 366 367net::IPEndPoint CreateUDPAddress(std::string ip_str, int port) { 368 net::IPAddressNumber ip_number; 369 CHECK(net::ParseIPLiteralToNumber(ip_str, &ip_number)); 370 return net::IPEndPoint(ip_number, port); 371} 372 373void DumpLoggingData(const media::cast::proto::LogMetadata& log_metadata, 374 const media::cast::FrameEventMap& frame_events, 375 const media::cast::PacketEventMap& packet_events, 376 bool compress, 377 base::ScopedFILE log_file) { 378 VLOG(0) << "Frame map size: " << frame_events.size(); 379 VLOG(0) << "Packet map size: " << packet_events.size(); 380 381 scoped_ptr<char[]> event_log(new char[media::cast::kMaxSerializedLogBytes]); 382 int event_log_bytes; 383 if (!media::cast::SerializeEvents(log_metadata, 384 frame_events, 385 packet_events, 386 compress, 387 media::cast::kMaxSerializedLogBytes, 388 event_log.get(), 389 &event_log_bytes)) { 390 VLOG(0) << "Failed to serialize events."; 391 return; 392 } 393 394 VLOG(0) << "Events serialized length: " << event_log_bytes; 395 396 int ret = fwrite(event_log.get(), 1, event_log_bytes, log_file.get()); 397 if (ret != event_log_bytes) 398 VLOG(0) << "Failed to write logs to file."; 399} 400 401void WriteLogsToFileAndStopSubscribing( 402 const scoped_refptr<media::cast::CastEnvironment>& cast_environment, 403 scoped_ptr<media::cast::EncodingEventSubscriber> video_event_subscriber, 404 scoped_ptr<media::cast::EncodingEventSubscriber> audio_event_subscriber, 405 base::ScopedFILE video_log_file, 406 base::ScopedFILE audio_log_file, 407 bool compress) { 408 cast_environment->Logging()->RemoveRawEventSubscriber( 409 video_event_subscriber.get()); 410 cast_environment->Logging()->RemoveRawEventSubscriber( 411 audio_event_subscriber.get()); 412 413 VLOG(0) << "Dumping logging data for video stream."; 414 media::cast::proto::LogMetadata log_metadata; 415 media::cast::FrameEventMap frame_events; 416 media::cast::PacketEventMap packet_events; 417 video_event_subscriber->GetEventsAndReset( 418 &log_metadata, &frame_events, &packet_events); 419 420 DumpLoggingData(log_metadata, 421 frame_events, 422 packet_events, 423 compress, 424 video_log_file.Pass()); 425 426 VLOG(0) << "Dumping logging data for audio stream."; 427 audio_event_subscriber->GetEventsAndReset( 428 &log_metadata, &frame_events, &packet_events); 429 430 DumpLoggingData(log_metadata, 431 frame_events, 432 packet_events, 433 compress, 434 audio_log_file.Pass()); 435} 436 437} // namespace 438 439int main(int argc, char** argv) { 440 base::AtExitManager at_exit; 441 CommandLine::Init(argc, argv); 442 InitLogging(logging::LoggingSettings()); 443 base::Thread test_thread("Cast sender test app thread"); 444 base::Thread audio_thread("Cast audio encoder thread"); 445 base::Thread video_thread("Cast video encoder thread"); 446 test_thread.Start(); 447 audio_thread.Start(); 448 video_thread.Start(); 449 450 base::MessageLoopForIO io_message_loop; 451 452 int remote_port; 453 media::cast::GetPort(&remote_port); 454 455 std::string remote_ip_address = 456 media::cast::GetIpAddress("Enter receiver IP."); 457 458 media::cast::AudioSenderConfig audio_config = 459 media::cast::GetAudioSenderConfig(); 460 media::cast::VideoSenderConfig video_config = 461 media::cast::GetVideoSenderConfig(); 462 463 // Running transport on the main thread. 464 // Setting up transport config. 465 media::cast::transport::CastTransportAudioConfig transport_audio_config; 466 media::cast::transport::CastTransportVideoConfig transport_video_config; 467 net::IPEndPoint remote_endpoint = 468 CreateUDPAddress(remote_ip_address, remote_port); 469 transport_audio_config.base.ssrc = audio_config.sender_ssrc; 470 VLOG(0) << "Audio ssrc: " << transport_audio_config.base.ssrc; 471 transport_audio_config.base.rtp_config = audio_config.rtp_config; 472 transport_video_config.base.ssrc = video_config.sender_ssrc; 473 transport_video_config.base.rtp_config = video_config.rtp_config; 474 475 // Enable raw event and stats logging. 476 // Running transport on the main thread. 477 scoped_refptr<media::cast::CastEnvironment> cast_environment( 478 new media::cast::CastEnvironment( 479 make_scoped_ptr<base::TickClock>(new base::DefaultTickClock()), 480 io_message_loop.message_loop_proxy(), 481 audio_thread.message_loop_proxy(), 482 video_thread.message_loop_proxy())); 483 484 scoped_ptr<media::cast::transport::CastTransportSender> transport_sender = 485 media::cast::transport::CastTransportSender::Create( 486 NULL, // net log. 487 cast_environment->Clock(), 488 remote_endpoint, 489 base::Bind(&UpdateCastTransportStatus), 490 base::Bind(&LogRawEvents, cast_environment), 491 base::TimeDelta::FromSeconds(1), 492 io_message_loop.message_loop_proxy()); 493 494 transport_sender->InitializeAudio(transport_audio_config); 495 transport_sender->InitializeVideo(transport_video_config); 496 497 scoped_ptr<media::cast::CastSender> cast_sender = 498 media::cast::CastSender::Create(cast_environment, transport_sender.get()); 499 500 cast_sender->InitializeVideo( 501 video_config, 502 base::Bind(&InitializationResult), 503 media::cast::CreateDefaultVideoEncodeAcceleratorCallback(), 504 media::cast::CreateDefaultVideoEncodeMemoryCallback()); 505 506 cast_sender->InitializeAudio(audio_config, base::Bind(&InitializationResult)); 507 508 transport_sender->SetPacketReceiver(cast_sender->packet_receiver()); 509 510 scoped_refptr<media::cast::AudioFrameInput> audio_frame_input = 511 cast_sender->audio_frame_input(); 512 scoped_refptr<media::cast::VideoFrameInput> video_frame_input = 513 cast_sender->video_frame_input(); 514 scoped_ptr<media::cast::SendProcess> send_process( 515 new media::cast::SendProcess(test_thread.message_loop_proxy(), 516 cast_environment->Clock(), 517 video_config, 518 audio_frame_input, 519 video_frame_input)); 520 521 // Set up event subscribers. 522 int logging_duration = media::cast::GetLoggingDuration(); 523 scoped_ptr<media::cast::EncodingEventSubscriber> video_event_subscriber; 524 scoped_ptr<media::cast::EncodingEventSubscriber> audio_event_subscriber; 525 if (logging_duration > 0) { 526 bool compress = media::cast::CompressLogs(); 527 std::string video_log_file_name( 528 media::cast::GetVideoLogFileDestination(compress)); 529 std::string audio_log_file_name( 530 media::cast::GetAudioLogFileDestination(compress)); 531 video_event_subscriber.reset(new media::cast::EncodingEventSubscriber( 532 media::cast::VIDEO_EVENT, 10000)); 533 audio_event_subscriber.reset(new media::cast::EncodingEventSubscriber( 534 media::cast::AUDIO_EVENT, 10000)); 535 cast_environment->Logging()->AddRawEventSubscriber( 536 video_event_subscriber.get()); 537 cast_environment->Logging()->AddRawEventSubscriber( 538 audio_event_subscriber.get()); 539 540 base::ScopedFILE video_log_file(fopen(video_log_file_name.c_str(), "w")); 541 if (!video_log_file) { 542 VLOG(1) << "Failed to open video log file for writing."; 543 exit(-1); 544 } 545 546 base::ScopedFILE audio_log_file(fopen(audio_log_file_name.c_str(), "w")); 547 if (!audio_log_file) { 548 VLOG(1) << "Failed to open audio log file for writing."; 549 exit(-1); 550 } 551 552 io_message_loop.message_loop_proxy()->PostDelayedTask( 553 FROM_HERE, 554 base::Bind(&WriteLogsToFileAndStopSubscribing, 555 cast_environment, 556 base::Passed(&video_event_subscriber), 557 base::Passed(&audio_event_subscriber), 558 base::Passed(&video_log_file), 559 base::Passed(&audio_log_file), 560 compress), 561 base::TimeDelta::FromSeconds(logging_duration)); 562 } 563 564 test_thread.message_loop_proxy()->PostTask( 565 FROM_HERE, 566 base::Bind(&media::cast::SendProcess::SendFrame, 567 base::Unretained(send_process.get()))); 568 569 io_message_loop.Run(); 570 571 return 0; 572} 573