diff options
Diffstat (limited to 'media/cast/test/sender.cc')
-rw-r--r-- | media/cast/test/sender.cc | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/media/cast/test/sender.cc b/media/cast/test/sender.cc new file mode 100644 index 0000000000..4e491bf6bf --- /dev/null +++ b/media/cast/test/sender.cc @@ -0,0 +1,346 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// 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 "base/at_exit.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "base/time/default_tick_clock.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/logging_defines.h" +#include "media/cast/test/audio_utility.h" +#include "media/cast/test/transport/transport.h" +#include "media/cast/test/utility/input_helper.h" +#include "media/cast/test/video_utility.h" +#include "ui/gfx/size.h" + +#define DEFAULT_SEND_PORT "2344" +#define DEFAULT_RECEIVE_PORT "2346" +#define DEFAULT_SEND_IP "127.0.0.1" +#define DEFAULT_READ_FROM_FILE "0" +#define DEFAULT_PACKET_LOSS "0" +#define DEFAULT_AUDIO_SENDER_SSRC "1" +#define DEFAULT_AUDIO_RECEIVER_SSRC "2" +#define DEFAULT_AUDIO_PAYLOAD_TYPE "127" +#define DEFAULT_VIDEO_SENDER_SSRC "11" +#define DEFAULT_VIDEO_RECEIVER_SSRC "12" +#define DEFAULT_VIDEO_PAYLOAD_TYPE "96" +#define DEFAULT_VIDEO_CODEC_WIDTH "1280" +#define DEFAULT_VIDEO_CODEC_HEIGHT "720" +#define DEFAULT_VIDEO_CODEC_BITRATE "2000" +#define DEFAULT_VIDEO_CODEC_MAX_BITRATE "4000" +#define DEFAULT_VIDEO_CODEC_MIN_BITRATE "1000" + +namespace media { +namespace cast { + +namespace { +static const int kAudioChannels = 2; +static const int kAudioSamplingFrequency = 48000; +static const int kSoundFrequency = 1234; // Frequency of sinusoid wave. +// The tests are commonly implemented with |kFrameTimerMs| RunTask function; +// a normal video is 30 fps hence the 33 ms between frames. +static const float kSoundVolume = 0.5f; +static const int kFrameTimerMs = 33; +} // namespace + +void GetPorts(int* tx_port, int* rx_port) { + test::InputBuilder tx_input("Enter send port.", + DEFAULT_SEND_PORT, 1, INT_MAX); + *tx_port = tx_input.GetIntInput(); + + test::InputBuilder rx_input("Enter receive port.", + DEFAULT_RECEIVE_PORT, 1, INT_MAX); + *rx_port = rx_input.GetIntInput(); +} + +int GetPacketLoss() { + test::InputBuilder input("Enter send side packet loss %.", + DEFAULT_PACKET_LOSS, 0, 99); + return input.GetIntInput(); +} + +std::string GetIpAddress(const std::string display_text) { + test::InputBuilder input(display_text, DEFAULT_SEND_IP, + INT_MIN, INT_MAX); + std::string ip_address = input.GetStringInput(); + // Verify correct form: + while (std::count(ip_address.begin(), ip_address.end(), '.') != 3) { + ip_address = input.GetStringInput(); + } + return ip_address; +} + +bool ReadFromFile() { + test::InputBuilder input("Enter 1 to read from file.", DEFAULT_READ_FROM_FILE, + 0, 1); + return (1 == input.GetIntInput()); +} + +std::string GetVideoFile() { + test::InputBuilder input("Enter file and path to raw video file.","", + INT_MIN, INT_MAX); + return input.GetStringInput(); +} + +void GetSsrcs(AudioSenderConfig* audio_config) { + test::InputBuilder input_tx("Choose audio sender SSRC.", + DEFAULT_AUDIO_SENDER_SSRC, 1, INT_MAX); + audio_config->sender_ssrc = input_tx.GetIntInput(); + + test::InputBuilder input_rx("Choose audio receiver SSRC.", + DEFAULT_AUDIO_RECEIVER_SSRC, 1, INT_MAX); + audio_config->incoming_feedback_ssrc = input_rx.GetIntInput(); +} + +void GetSsrcs(VideoSenderConfig* video_config) { + test::InputBuilder input_tx("Choose video sender SSRC.", + DEFAULT_VIDEO_SENDER_SSRC, 1, INT_MAX); + video_config->sender_ssrc = input_tx.GetIntInput(); + + test::InputBuilder input_rx("Choose video receiver SSRC.", + DEFAULT_VIDEO_RECEIVER_SSRC, 1, INT_MAX); + video_config->incoming_feedback_ssrc = input_rx.GetIntInput(); +} + +void GetPayloadtype(AudioSenderConfig* audio_config) { + test::InputBuilder input("Choose audio sender payload type.", + DEFAULT_AUDIO_PAYLOAD_TYPE, 96, 127); + audio_config->rtp_payload_type = input.GetIntInput(); +} + +AudioSenderConfig GetAudioSenderConfig() { + AudioSenderConfig audio_config; + + GetSsrcs(&audio_config); + GetPayloadtype(&audio_config); + + audio_config.rtcp_c_name = "audio_sender@a.b.c.d"; + + VLOG(0) << "Using OPUS 48Khz stereo at 64kbit/s"; + audio_config.use_external_encoder = false; + audio_config.frequency = kAudioSamplingFrequency; + audio_config.channels = kAudioChannels; + audio_config.bitrate = 64000; + audio_config.codec = kOpus; + return audio_config; +} + +void GetPayloadtype(VideoSenderConfig* video_config) { + test::InputBuilder input("Choose video sender payload type.", + DEFAULT_VIDEO_PAYLOAD_TYPE, 96, 127); + video_config->rtp_payload_type = input.GetIntInput(); +} + +void GetVideoCodecSize(VideoSenderConfig* video_config) { + test::InputBuilder input_width("Choose video width.", + DEFAULT_VIDEO_CODEC_WIDTH, 144, 1920); + video_config->width = input_width.GetIntInput(); + + test::InputBuilder input_height("Choose video height.", + DEFAULT_VIDEO_CODEC_HEIGHT, 176, 1080); + video_config->height = input_height.GetIntInput(); +} + +void GetVideoBitrates(VideoSenderConfig* video_config) { + test::InputBuilder input_start_br("Choose start bitrate[kbps].", + DEFAULT_VIDEO_CODEC_BITRATE, 0, INT_MAX); + video_config->start_bitrate = input_start_br.GetIntInput() * 1000; + + test::InputBuilder input_max_br("Choose max bitrate[kbps].", + DEFAULT_VIDEO_CODEC_MAX_BITRATE, 0, INT_MAX); + video_config->max_bitrate = input_max_br.GetIntInput() * 1000; + + test::InputBuilder input_min_br("Choose min bitrate[kbps].", + DEFAULT_VIDEO_CODEC_MIN_BITRATE, 0, INT_MAX); + video_config->min_bitrate = input_min_br.GetIntInput() * 1000; +} + +VideoSenderConfig GetVideoSenderConfig() { + VideoSenderConfig video_config; + + GetSsrcs(&video_config); + GetPayloadtype(&video_config); + GetVideoCodecSize(&video_config); + GetVideoBitrates(&video_config); + + video_config.rtcp_c_name = "video_sender@a.b.c.d"; + + video_config.use_external_encoder = false; + + VLOG(0) << "Using VP8 at 30 fps"; + video_config.min_qp = 4; + video_config.max_qp = 40; + video_config.max_frame_rate = 30; + video_config.codec = kVp8; + video_config.max_number_of_video_buffers_used = 1; + video_config.number_of_cores = 1; + return video_config; +} + +class SendProcess { + public: + SendProcess(scoped_refptr<CastEnvironment> cast_environment, + const VideoSenderConfig& video_config, + FrameInput* frame_input) + : video_config_(video_config), + audio_diff_(kFrameTimerMs), + frame_input_(frame_input), + synthetic_count_(0), + clock_(cast_environment->Clock()), + start_time_(), + send_time_(), + weak_factory_(this) { + audio_bus_factory_.reset(new TestAudioBusFactory(kAudioChannels, + kAudioSamplingFrequency, kSoundFrequency, kSoundVolume)); + if (ReadFromFile()) { + std::string video_file_name = GetVideoFile(); + video_file_ = fopen(video_file_name.c_str(), "r"); + if (video_file_ == NULL) { + VLOG(1) << "Failed to open file"; + exit(-1); + } + } else { + video_file_ = NULL; + } + } + + ~SendProcess() { + if (video_file_) + fclose(video_file_); + } + + void ReleaseVideoFrame(const scoped_refptr<media::VideoFrame>&) { + SendFrame(); + } + + void SendFrame() { + // Make sure that we don't drift. + int num_10ms_blocks = audio_diff_ / 10; + // Avoid drift. + audio_diff_ += kFrameTimerMs - num_10ms_blocks * 10; + + scoped_ptr<AudioBus> audio_bus(audio_bus_factory_->NextAudioBus( + base::TimeDelta::FromMilliseconds(10) * num_10ms_blocks)); + AudioBus* const audio_bus_ptr = audio_bus.get(); + frame_input_->InsertAudio(audio_bus_ptr, clock_->NowTicks(), + base::Bind(base::DoNothing)); + + gfx::Size size(video_config_.width, video_config_.height); + // TODO(mikhal): Use the provided timestamp. + if (start_time_.is_null()) + start_time_ = clock_->NowTicks(); + base::TimeDelta time_diff = clock_->NowTicks() - start_time_; + scoped_refptr<media::VideoFrame> video_frame = + media::VideoFrame::CreateFrame( + VideoFrame::I420, size, gfx::Rect(size), size, time_diff); + if (video_file_) { + if (!PopulateVideoFrameFromFile(video_frame, video_file_)) + return; + } else { + PopulateVideoFrame(video_frame, synthetic_count_); + ++synthetic_count_; + } + + // Time the sending of the frame to match the set frame rate. + // Sleep if that time has yet to elapse. + base::TimeTicks now = clock_->NowTicks(); + base::TimeDelta video_frame_time = + base::TimeDelta::FromMilliseconds(kFrameTimerMs); + base::TimeDelta elapsed_time = now - send_time_; + if (elapsed_time < video_frame_time) { + base::PlatformThread::Sleep(video_frame_time - elapsed_time); + VLOG(1) << "Sleep" << + (video_frame_time - elapsed_time).InMilliseconds(); + } + + send_time_ = clock_->NowTicks(); + frame_input_->InsertRawVideoFrame(video_frame, send_time_, + base::Bind(&SendProcess::ReleaseVideoFrame, weak_factory_.GetWeakPtr(), + video_frame)); + } + + private: + const VideoSenderConfig video_config_; + int audio_diff_; + const scoped_refptr<FrameInput> frame_input_; + FILE* video_file_; + uint8 synthetic_count_; + base::TickClock* const clock_; // Not owned by this class. + base::TimeTicks start_time_; + base::TimeTicks send_time_; + scoped_ptr<TestAudioBusFactory> audio_bus_factory_; + base::WeakPtrFactory<SendProcess> weak_factory_; +}; + +} // namespace cast +} // namespace media + + +int main(int argc, char** argv) { + base::AtExitManager at_exit; + VLOG(1) << "Cast Sender"; + base::Thread main_thread("Cast main send thread"); + base::Thread audio_thread("Cast audio encoder thread"); + base::Thread video_thread("Cast video encoder thread"); + main_thread.Start(); + audio_thread.Start(); + video_thread.Start(); + + base::DefaultTickClock clock; + base::MessageLoopForIO io_message_loop; + + // Enable main and send side threads only. Disable logging. + scoped_refptr<media::cast::CastEnvironment> cast_environment(new + media::cast::CastEnvironment( + &clock, + main_thread.message_loop_proxy(), + audio_thread.message_loop_proxy(), + NULL, + video_thread.message_loop_proxy(), + NULL, + media::cast::GetDefaultCastLoggingConfig())); + + media::cast::AudioSenderConfig audio_config = + media::cast::GetAudioSenderConfig(); + media::cast::VideoSenderConfig video_config = + media::cast::GetVideoSenderConfig(); + + scoped_ptr<media::cast::test::Transport> transport( + new media::cast::test::Transport(io_message_loop.message_loop_proxy())); + scoped_ptr<media::cast::CastSender> cast_sender( + media::cast::CastSender::CreateCastSender(cast_environment, + audio_config, + video_config, + NULL, // VideoEncoderController. + transport->packet_sender())); + + media::cast::PacketReceiver* packet_receiver = cast_sender->packet_receiver(); + + int send_to_port, receive_port; + media::cast::GetPorts(&send_to_port, &receive_port); + std::string ip_address = media::cast::GetIpAddress("Enter destination IP."); + std::string local_ip_address = media::cast::GetIpAddress("Enter local IP."); + int packet_loss_percentage = media::cast::GetPacketLoss(); + + transport->SetLocalReceiver(packet_receiver, ip_address, local_ip_address, + receive_port); + transport->SetSendDestination(ip_address, send_to_port); + transport->SetSendSidePacketLoss(packet_loss_percentage); + + media::cast::FrameInput* frame_input = cast_sender->frame_input(); + scoped_ptr<media::cast::SendProcess> send_process(new + media::cast::SendProcess(cast_environment, video_config, frame_input)); + + send_process->SendFrame(); + io_message_loop.Run(); + transport->StopReceiving(); + return 0; +} |