diff options
author | mikhal@google.com <mikhal@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-08 22:48:43 +0000 |
---|---|---|
committer | mikhal@google.com <mikhal@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-08 22:48:43 +0000 |
commit | 47be950b3633d281a18df0cec46b12e84ab30c56 (patch) | |
tree | 1e27eea4a7b60a1cf5a0a381dd13d551efa99aa9 /media | |
parent | c7a0e41db161f3eab8a61e133d81ddd688c354f3 (diff) | |
download | chromium_src-47be950b3633d281a18df0cec46b12e84ab30c56.zip chromium_src-47be950b3633d281a18df0cec46b12e84ab30c56.tar.gz chromium_src-47be950b3633d281a18df0cec46b12e84ab30c56.tar.bz2 |
Adding send and receive test apps to Cast
Allowing the user to simulate full cast sender and receiver.
Sender data can either be from file or synthetically generated.
When in Linux, video will be displayed.
Review URL: https://codereview.chromium.org/51213006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@234020 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/cast/DEPS | 1 | ||||
-rw-r--r-- | media/cast/cast.gyp | 54 | ||||
-rw-r--r-- | media/cast/test/linux_output_window.cc | 134 | ||||
-rw-r--r-- | media/cast/test/linux_output_window.h | 49 | ||||
-rw-r--r-- | media/cast/test/receiver.cc | 238 | ||||
-rw-r--r-- | media/cast/test/sender.cc | 325 | ||||
-rw-r--r-- | media/cast/test/transport/transport.cc | 14 | ||||
-rw-r--r-- | media/cast/test/transport/transport.h | 2 | ||||
-rw-r--r-- | media/cast/test/utility/input_helper.cc | 11 | ||||
-rw-r--r-- | media/cast/test/utility/input_helper.h | 5 | ||||
-rw-r--r-- | media/cast/test/utility/utility.gyp | 8 | ||||
-rw-r--r-- | media/cast/test/video_utility.cc | 32 | ||||
-rw-r--r-- | media/cast/test/video_utility.h | 4 | ||||
-rw-r--r-- | media/cast/video_sender/video_sender.cc | 1 |
14 files changed, 862 insertions, 16 deletions
diff --git a/media/cast/DEPS b/media/cast/DEPS index 8e10c67..9ac2619 100644 --- a/media/cast/DEPS +++ b/media/cast/DEPS @@ -1,4 +1,5 @@ include_rules = [ "+net", "+third_party/webrtc", + "+third_party/libyuv", ] diff --git a/media/cast/cast.gyp b/media/cast/cast.gyp index 99d1794..51d8205 100644 --- a/media/cast/cast.gyp +++ b/media/cast/cast.gyp @@ -33,6 +33,7 @@ 'cast_receiver.gyp:cast_receiver', 'cast_sender.gyp:cast_sender', 'logging/logging.gyp:cast_logging', + 'test/utility/utility.gyp:cast_test_utility', '<(DEPTH)/base/base.gyp:run_all_unittests', '<(DEPTH)/net/net.gyp:net', '<(DEPTH)/testing/gmock.gyp:gmock', @@ -71,11 +72,8 @@ 'rtp_sender/rtp_packetizer/rtp_packetizer_unittest.cc', 'rtp_sender/rtp_packetizer/test/rtp_header_parser.cc', 'rtp_sender/rtp_packetizer/test/rtp_header_parser.h', - 'test/audio_utility.cc', 'test/encode_decode_test.cc', 'test/end2end_unittest.cc', - 'test/fake_task_runner.cc', - 'test/video_utility.cc', 'video_receiver/video_decoder_unittest.cc', 'video_receiver/video_receiver_unittest.cc', 'video_sender/mock_video_encoder_controller.cc', @@ -88,6 +86,56 @@ 'video_sender/video_sender_unittest.cc', ], # source }, + { + 'target_name': 'cast_sender_app', + 'type': 'executable', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + '<(DEPTH)/net/net.gyp:net_test_support', + 'cast_config', + '<(DEPTH)/media/cast/cast_sender.gyp:*', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/opus/opus.gyp:opus', + '<(DEPTH)/media/cast/test/transport/transport.gyp:cast_transport', + '<(DEPTH)/media/cast/test/utility/utility.gyp:cast_test_utility', + ], + 'sources': [ + '<(DEPTH)/media/cast/test/sender.cc', + ], + }, + { + 'target_name': 'cast_receiver_app', + 'type': 'executable', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + '<(DEPTH)/net/net.gyp:net_test_support', + 'cast_config', + '<(DEPTH)/media/cast/cast_receiver.gyp:*', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/media/cast/test/transport/transport.gyp:cast_transport', + '<(DEPTH)/media/cast/test/utility/utility.gyp:cast_test_utility', + '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', + ], + 'sources': [ + '<(DEPTH)/media/cast/test/receiver.cc', + ], + 'conditions': [ + ['OS == "linux"', { + 'sources': [ + '<(DEPTH)/media/cast/test/linux_output_window.cc', + '<(DEPTH)/media/cast/test/linux_output_window.h', + ], + 'libraries': [ + '-lXext', + '-lX11', + ], + }], + ], + }, ], # targets }], # include_tests ], diff --git a/media/cast/test/linux_output_window.cc b/media/cast/test/linux_output_window.cc new file mode 100644 index 0000000..f7405e0 --- /dev/null +++ b/media/cast/test/linux_output_window.cc @@ -0,0 +1,134 @@ +// 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. + +#include "media/cast/test/linux_output_window.h" + +#include "base/logging.h" +#include "third_party/libyuv/include/libyuv/convert.h" + +namespace media { +namespace cast { +namespace test { + +LinuxOutputWindow::LinuxOutputWindow(int x_pos, + int y_pos, + int width, + int height, + const std::string& name) { + CreateWindow( x_pos, y_pos, width, height, name); +} + +LinuxOutputWindow::~LinuxOutputWindow() { + if (display_ && window_) { + XUnmapWindow(display_, window_); + XDestroyWindow(display_, window_); + XSync(display_, false); + if (gc_) + XFreeGC(display_, gc_); + XCloseDisplay(display_); + } +} + +void LinuxOutputWindow::CreateWindow(int x_pos, + int y_pos, + int width, + int height, + const std::string& name) { + display_ = XOpenDisplay(NULL); + if (display_ == NULL) { + // There's no point to continue if this happens: nothing will work anyway. + VLOG(1) << "Failed to connect to X server: X environment likely broken"; + NOTREACHED(); + } + + int screen = DefaultScreen(display_); + + // Try to establish a 24-bit TrueColor display. + // (our environment must allow this). + XVisualInfo visual_info; + if (XMatchVisualInfo(display_, screen, 24, TrueColor, &visual_info) == 0) { + VLOG(1) << "Failed to establish 24-bit TrueColor in X environment."; + NOTREACHED(); + } + + // Create suitable window attributes. + XSetWindowAttributes window_attributes; + window_attributes.colormap = XCreateColormap( + display_, DefaultRootWindow(display_), visual_info.visual, AllocNone); + window_attributes.event_mask = StructureNotifyMask | ExposureMask; + window_attributes.background_pixel = 0; + window_attributes.border_pixel = 0; + + unsigned long attribute_mask = CWBackPixel | CWBorderPixel | CWColormap | + CWEventMask; + + window_ = XCreateWindow(display_, DefaultRootWindow(display_), x_pos, + y_pos, width, height, 0, visual_info.depth, + InputOutput, visual_info.visual, + attribute_mask, &window_attributes); + + // Set window name. + XStoreName(display_, window_, name.c_str()); + XSetIconName(display_, window_, name.c_str()); + + // Make x report events for mask. + XSelectInput(display_, window_, StructureNotifyMask); + + // Map the window to the display. + XMapWindow(display_, window_); + + // Wait for map event. + XEvent event; + do { + XNextEvent(display_, &event); + } while (event.type != MapNotify || event.xmap.event != window_); + + gc_ = XCreateGC(display_, window_, 0, 0); + + // create shared memory image + image_ = XShmCreateImage(display_, CopyFromParent, 24, ZPixmap, NULL, + &shminfo_, width, height); + shminfo_.shmid = shmget(IPC_PRIVATE, + (image_->bytes_per_line * image_->height), + IPC_CREAT | 0777); + shminfo_.shmaddr = image_->data = (char*) shmat(shminfo_.shmid, 0, 0); + if (image_->data == reinterpret_cast<char*>(-1)) { + VLOG(1) << "XShmCreateImage failed"; + NOTREACHED(); + } + render_buffer_ = reinterpret_cast<uint8_t*>(image_->data); + shminfo_.readOnly = false; + + // Attach image to display. + if (!XShmAttach(display_, &shminfo_)) { + VLOG(1) << "XShmAttach failed"; + NOTREACHED(); + } + XSync(display_, false); +} + +void LinuxOutputWindow::RenderFrame(const I420VideoFrame& video_frame) { + libyuv::I420ToARGB(video_frame.y_plane.data, + video_frame.y_plane.stride, + video_frame.u_plane.data, + video_frame.u_plane.stride, + video_frame.v_plane.data, + video_frame.v_plane.stride, + render_buffer_, + video_frame.width * 4, // Stride. + video_frame.width, + video_frame.height); + + // Place image in window. + XShmPutImage(display_, window_, gc_, image_, 0, 0, 0, 0, + video_frame.width, + video_frame.height, true); + + // Very important for the image to update properly! + XSync(display_, false); +} + +} // namespace test +} // namespace cast +} // namespace media
\ No newline at end of file diff --git a/media/cast/test/linux_output_window.h b/media/cast/test/linux_output_window.h new file mode 100644 index 0000000..ffaba3a --- /dev/null +++ b/media/cast/test/linux_output_window.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef MEDIA_CAST_TEST_LINUX_OUTPUT_WINDOW_H_ +#define MEDIA_CAST_TEST_LINUX_OUTPUT_WINDOW_H_ + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/extensions/XShm.h> + +#include "media/cast/cast_config.h" + +namespace media { +namespace cast { +namespace test { + +class LinuxOutputWindow { + public: + LinuxOutputWindow(int x_pos, + int y_pos, + int width, + int height, + const std::string& name); + virtual ~LinuxOutputWindow(); + + void RenderFrame(const I420VideoFrame& video_frame); + + private: + void CreateWindow(int x_pos, + int y_pos, + int width, + int height, + const std::string& name); + uint8* render_buffer_; + Display* display_; + Window window_; + XShmSegmentInfo shminfo_; + GC gc_; + XImage* image_; +}; + +} // namespace test +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TEST_LINUX_OUTPUT_WINDOW_H_ diff --git a/media/cast/test/receiver.cc b/media/cast/test/receiver.cc new file mode 100644 index 0000000..56ede58 --- /dev/null +++ b/media/cast/test/receiver.cc @@ -0,0 +1,238 @@ +// 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. + +#include <algorithm> +#include <climits> +#include <cstdarg> +#include <cstdio> +#include <string> + +#include "base/at_exit.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/sequenced_task_runner.h" +#include "base/time/default_tick_clock.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_environment.h" +#include "media/cast/cast_receiver.h" +#include "media/cast/test/transport/transport.h" +#include "media/cast/test/utility/input_helper.h" + +#if defined(OS_LINUX) +#include "media/cast/test/linux_output_window.h" +#endif // OS_LINUX + +namespace media { +namespace cast { + +#define DEFAULT_SEND_PORT "2346" +#define DEFAULT_RECEIVE_PORT "2344" +#define DEFAULT_SEND_IP "127.0.0.1" +#define DEFAULT_RESTART "0" +#define DEFAULT_AUDIO_FEEDBACK_SSRC "2" +#define DEFAULT_AUDIO_INCOMING_SSRC "1" +#define DEFAULT_AUDIO_PAYLOAD_TYPE "127" +#define DEFAULT_VIDEO_FEEDBACK_SSRC "12" +#define DEFAULT_VIDEO_INCOMING_SSRC "11" +#define DEFAULT_VIDEO_PAYLOAD_TYPE "96" +#define DEFAULT_VIDEO_CODEC_WIDTH "640" +#define DEFAULT_VIDEO_CODEC_HEIGHT "480" +#define DEFAULT_VIDEO_CODEC_BITRATE "2000" + +static const int kAudioSamplingFrequency = 48000; +#if defined(OS_LINUX) +const int kVideoWindowWidth = 1280; +const int kVideoWindowHeight = 720; +#endif // OS_LINUX +static const int kFrameTimerMs = 33; + + +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(); +} + +std::string GetIpAddress() { + test::InputBuilder input("Enter destination IP.",DEFAULT_SEND_IP, + INT_MIN, INT_MAX); + std::string ip_address = input.GetStringInput(); + // Ensure correct form: + while (std::count(ip_address.begin(), ip_address.end(), '.') != 3) { + ip_address = input.GetStringInput(); + } + return ip_address; +} + +void GetSsrcs(AudioReceiverConfig* audio_config) { + test::InputBuilder input_tx("Choose audio sender SSRC.", + DEFAULT_AUDIO_FEEDBACK_SSRC, 1, INT_MAX); + audio_config->feedback_ssrc = input_tx.GetIntInput(); + + test::InputBuilder input_rx("Choose audio receiver SSRC.", + DEFAULT_AUDIO_INCOMING_SSRC, 1, INT_MAX); + audio_config->incoming_ssrc = input_tx.GetIntInput(); +} + +void GetSsrcs(VideoReceiverConfig* video_config) { + test::InputBuilder input_tx("Choose video sender SSRC.", + DEFAULT_VIDEO_FEEDBACK_SSRC, 1, INT_MAX); + video_config->feedback_ssrc = input_tx.GetIntInput(); + + test::InputBuilder input_rx("Choose video receiver SSRC.", + DEFAULT_VIDEO_INCOMING_SSRC, 1, INT_MAX); + video_config->incoming_ssrc = input_rx.GetIntInput(); +} + +void GetPayloadtype(AudioReceiverConfig* audio_config) { + test::InputBuilder input("Choose audio receiver payload type.", + DEFAULT_AUDIO_PAYLOAD_TYPE, 96, 127); + audio_config->rtp_payload_type = input.GetIntInput(); +} + +AudioReceiverConfig GetAudioReceiverConfig() { + AudioReceiverConfig audio_config; + + GetSsrcs(&audio_config); + GetPayloadtype(&audio_config); + + audio_config.rtcp_c_name = "audio_receiver@a.b.c.d"; + + VLOG(1) << "Using OPUS 48Khz stereo"; + audio_config.use_external_decoder = false; + audio_config.frequency = 48000; + audio_config.channels = 2; + audio_config.codec = kOpus; + return audio_config; +} + +void GetPayloadtype(VideoReceiverConfig* video_config) { + test::InputBuilder input("Choose video receiver payload type.", + DEFAULT_VIDEO_PAYLOAD_TYPE, 96, 127); + video_config->rtp_payload_type = input.GetIntInput(); +} + +VideoReceiverConfig GetVideoReceiverConfig() { + VideoReceiverConfig video_config; + + GetSsrcs(&video_config); + GetPayloadtype(&video_config); + + video_config.rtcp_c_name = "video_receiver@a.b.c.d"; + + video_config.use_external_decoder = false; + + VLOG(1) << "Using VP8"; + video_config.codec = kVp8; + return video_config; +} + + +class ReceiveProcess { + public: + ReceiveProcess(scoped_refptr<CastEnvironment> cast_environment, + scoped_refptr<FrameReceiver> frame_receiver) + : cast_environment_(cast_environment), + frame_receiver_(frame_receiver), +#if defined(OS_LINUX) + render_(0, 0, kVideoWindowWidth, kVideoWindowHeight, "Cast_receiver"), +#endif // OS_LINUX + last_playout_time_(), + weak_factory_(this) {} + + void Start() { + GetFrame(); + } + + private: + void DisplayFrame(scoped_ptr<I420VideoFrame> frame, + const base::TimeTicks& render_time) { +#ifdef OS_LINUX + render_.RenderFrame(*frame); +#endif // OS_LINUX + GetFrame(); + } + + void ReceiveAudioFrame(scoped_ptr<PcmAudioFrame> audio_frame, + const base::TimeTicks& playout_time) { + // For audio just print the playout delta between audio frames. + if (!last_playout_time_.is_null()){ + base::TimeDelta time_diff = playout_time - last_playout_time_; + VLOG(0) << " PlayoutDelay[mS] = " << time_diff.InMilliseconds(); + } + last_playout_time_ = playout_time; + GetFrame(); + } + + void GetFrame() { + frame_receiver_->GetRawVideoFrame( + base::Bind(&ReceiveProcess::DisplayFrame, weak_factory_.GetWeakPtr())); + int num_10ms_blocks = kFrameTimerMs / 10; + frame_receiver_->GetRawAudioFrame(num_10ms_blocks, kAudioSamplingFrequency, + base::Bind(&ReceiveProcess::ReceiveAudioFrame, + weak_factory_.GetWeakPtr())); + } + + const scoped_refptr<CastEnvironment> cast_environment_; + const scoped_refptr<FrameReceiver> frame_receiver_; +#ifdef OS_LINUX + test::LinuxOutputWindow render_; +#endif // OS_LINUX + base::TimeTicks last_playout_time_; + base::WeakPtrFactory<ReceiveProcess> weak_factory_; +}; + +} // namespace cast +} // namespace media + +int main(int argc, char** argv) { + base::AtExitManager at_exit; + base::MessageLoopForIO main_message_loop; + VLOG(1) << "Cast Receiver"; + + // Set up environment. + base::DefaultTickClock clock; + scoped_refptr<base::SequencedTaskRunner> + task_runner(main_message_loop.message_loop_proxy()); + scoped_refptr<media::cast::CastEnvironment> cast_environment(new + media::cast::CastEnvironment(&clock, + task_runner, NULL, task_runner, NULL, task_runner)); + + media::cast::AudioReceiverConfig audio_config = + media::cast::GetAudioReceiverConfig(); + media::cast::VideoReceiverConfig video_config = + media::cast::GetVideoReceiverConfig(); + + scoped_ptr<media::cast::test::Transport> transport( + new media::cast::test::Transport(cast_environment)); + scoped_ptr<media::cast::CastReceiver> cast_receiver( + media::cast::CastReceiver::CreateCastReceiver( + cast_environment, + audio_config, + video_config, + transport->packet_sender())); + + media::cast::PacketReceiver* packet_receiver = + cast_receiver->packet_receiver(); + + media::cast::FrameReceiver* frame_receiver = cast_receiver->frame_receiver(); + + int send_to_port, receive_port; + media::cast::GetPorts(&send_to_port, &receive_port); + std::string ip_address = media::cast::GetIpAddress(); + transport->SetLocalReceiver(packet_receiver, ip_address, receive_port); + transport->SetSendDestination(ip_address, send_to_port); + + media::cast::ReceiveProcess receive_process(cast_environment, frame_receiver); + receive_process.Start(); + main_message_loop.Run(); + transport->StopReceiving(); + return 0; +} diff --git a/media/cast/test/sender.cc b/media/cast/test/sender.cc new file mode 100644 index 0000000..a489ffa --- /dev/null +++ b/media/cast/test/sender.cc @@ -0,0 +1,325 @@ +// 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 <cmath> + +#include "base/at_exit.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "base/task_runner.h" +#include "base/time/default_tick_clock.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_environment.h" +#include "media/cast/cast_sender.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" + +#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; + +// Dummy callback function that does nothing except to accept ownership of +// |audio_bus| for destruction. +void OwnThatAudioBus(scoped_ptr<AudioBus> audio_bus) { +} +} // 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() { + test::InputBuilder input("Enter destination IP.",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) + : cast_environment_(cast_environment), + video_config_(video_config), + audio_diff_(kFrameTimerMs), + frame_input_(frame_input), + synthetic_count_(0), + clock_(cast_environment->Clock()), + 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 I420VideoFrame* frame) { + delete [] frame->y_plane.data; + delete [] frame->u_plane.data; + delete [] frame->v_plane.data; + SendFrame(); + } + + void ReleaseAudioFrame(const PcmAudioFrame* frame) { + delete frame; + } + + 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(&OwnThatAudioBus, base::Passed(&audio_bus))); + + I420VideoFrame* video_frame = new I420VideoFrame(); + video_frame->width = video_config_.width; + video_frame->height = video_config_.height; + if (video_file_) { + if (!PopulateVideoFrameFromFile(video_frame, video_file_)) + return; + } else { + PopulateVideoFrame(video_frame, synthetic_count_); + ++synthetic_count_; + } + + frame_input_->InsertRawVideoFrame(video_frame, clock_->NowTicks(), + base::Bind(&SendProcess::ReleaseVideoFrame, weak_factory_.GetWeakPtr(), + video_frame)); + } + + private: + const scoped_refptr<CastEnvironment> cast_environment_; + 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. + 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::DefaultTickClock clock; + base::MessageLoopForIO main_message_loop; + scoped_refptr<base::SequencedTaskRunner> + task_runner(main_message_loop.message_loop_proxy()); + + // Enable main and send side threads only. + scoped_refptr<media::cast::CastEnvironment> cast_environment(new + media::cast::CastEnvironment(&clock, task_runner, task_runner, NULL, + task_runner, NULL)); + + 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(cast_environment)); + 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(); + int packet_loss_percentage = media::cast::GetPacketLoss(); + + transport->SetLocalReceiver(packet_receiver, 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(); + main_message_loop.Run(); + transport->StopReceiving(); + return 0; +} diff --git a/media/cast/test/transport/transport.cc b/media/cast/test/transport/transport.cc index b5cc2f5..74b50f7 100644 --- a/media/cast/test/transport/transport.cc +++ b/media/cast/test/transport/transport.cc @@ -49,16 +49,16 @@ class LocalUdpTransportData { // Got a packet with length result. uint8* data = reinterpret_cast<uint8*>(buffer_->data()); packet_receiver_->ReceivedPacket(data, result, - base::Bind(&LocalUdpTransportData::DeletePacket, - weak_factory_.GetWeakPtr(), data)); + base::Bind(&LocalUdpTransportData::RecvFromSocketLoop, + weak_factory_.GetWeakPtr())); } void RecvFromSocketLoop() { // Callback should always trigger with a packet. int res = udp_socket_->RecvFrom(buffer_.get(), kMaxPacketSize, - &bind_address_, base::Bind(&LocalUdpTransportData::RecvFromSocketLoop, + &bind_address_, base::Bind(&LocalUdpTransportData::PacketReceived, weak_factory_.GetWeakPtr())); - DCHECK(res >= net::IO_PENDING); + DCHECK(res >= net::ERR_IO_PENDING); if (res > 0) { PacketReceived(res); } @@ -88,7 +88,7 @@ class LocalPacketSender : public PacketSender { send_address_(), loss_limit_(0) {} - virtual bool SendPacket(const Packet& packet) { + virtual bool SendPacket(const Packet& packet) OVERRIDE { const uint8* data = packet.data(); if (loss_limit_ > 0) { int r = base::RandInt(0, 100); @@ -107,7 +107,7 @@ class LocalPacketSender : public PacketSender { return (rv == packet.size()); } - virtual bool SendPackets(const PacketList& packets) { + virtual bool SendPackets(const PacketList& packets) OVERRIDE { bool out_val = true; for (size_t i = 0; i < packets.size(); ++i) { const Packet& packet = packets[i]; @@ -173,4 +173,4 @@ void Transport::SetSendDestination(std::string ip_address, int port) { } // namespace test } // namespace cast -} // namespace media
\ No newline at end of file +} // namespace media diff --git a/media/cast/test/transport/transport.h b/media/cast/test/transport/transport.h index 0a9f5cb..fe2303d 100644 --- a/media/cast/test/transport/transport.h +++ b/media/cast/test/transport/transport.h @@ -52,4 +52,4 @@ class Transport { } // namespace cast } // namespace media -#endif // MEDIA_CAST_TEST_TRANSPORT_TRANSPORT_H_
\ No newline at end of file +#endif // MEDIA_CAST_TEST_TRANSPORT_TRANSPORT_H_ diff --git a/media/cast/test/utility/input_helper.cc b/media/cast/test/utility/input_helper.cc index 3af5eb0..fa93926 100644 --- a/media/cast/test/utility/input_helper.cc +++ b/media/cast/test/utility/input_helper.cc @@ -25,7 +25,7 @@ InputBuilder::InputBuilder(const std::string& title, InputBuilder::~InputBuilder() {} -std::string InputBuilder::GetInput() const { +std::string InputBuilder::GetStringInput() const { printf("\n%s\n", title_.c_str()); if (!default_value_.empty()) printf("Hit enter for default (%s):\n", default_value_.c_str()); @@ -45,11 +45,18 @@ std::string InputBuilder::GetInput() const { if (!ValidateInput(input)) { printf("Invalid input. Please try again.\n"); - return GetInput(); + return GetStringInput(); } return input; } +int InputBuilder::GetIntInput() const { + std::string string_input = GetStringInput(); + int int_value; + CHECK(base::StringToInt(string_input, &int_value)); + return int_value; +} + bool InputBuilder::ValidateInput(const std::string input) const { // Check for a valid range. if (low_range_ == INT_MIN && high_range_ == INT_MAX) return true; diff --git a/media/cast/test/utility/input_helper.h b/media/cast/test/utility/input_helper.h index 53add86..baabe51 100644 --- a/media/cast/test/utility/input_helper.h +++ b/media/cast/test/utility/input_helper.h @@ -27,7 +27,10 @@ class InputBuilder { // the answer. This method will keep asking the user until a correct answer // is returned and is thereby guaranteed to return a response that is // acceptable within the predefined range. - std::string GetInput() const; + // Input will be returned in either string or int format, base on the function + // called. + std::string GetStringInput() const; + int GetIntInput() const; private: bool ValidateInput(const std::string input) const; diff --git a/media/cast/test/utility/utility.gyp b/media/cast/test/utility/utility.gyp index fbe2e4d..df841c3 100644 --- a/media/cast/test/utility/utility.gyp +++ b/media/cast/test/utility/utility.gyp @@ -10,9 +10,15 @@ 'include_dirs': [ '<(DEPTH)/', ], + 'dependencies': [ + '<(DEPTH)/testing/gtest.gyp:gtest', + ], 'sources': [ 'input_helper.cc', - 'input_helper.', + 'input_helper.h', + '<(DEPTH)/media/cast/test/audio_utility.cc', + '<(DEPTH)/media/cast/test/fake_task_runner.cc', + '<(DEPTH)/media/cast/test/video_utility.cc', ], # source }, ], diff --git a/media/cast/test/video_utility.cc b/media/cast/test/video_utility.cc index 156329a..e1802ee 100644 --- a/media/cast/test/video_utility.cc +++ b/media/cast/test/video_utility.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include <math.h> +#include <cstdio> #include "media/cast/test/video_utility.h" @@ -94,5 +95,36 @@ void PopulateVideoFrame(I420VideoFrame* frame, int start_value) { } } +bool PopulateVideoFrameFromFile(I420VideoFrame* frame, FILE* video_file) { + int half_width = (frame->width + 1) / 2; + int half_height = (frame->height + 1) / 2; + size_t frame_size = + frame->width * frame->height + 2 * half_width * half_height; + + uint8* raw_data = new uint8[frame_size]; + size_t count = fread(raw_data, 1, frame_size, video_file); + if (count != frame_size) return false; + + frame->y_plane.stride = frame->width; + frame->y_plane.length = frame->width * frame->height; + frame->y_plane.data = new uint8[frame->y_plane.length]; + + frame->u_plane.stride = half_width; + frame->u_plane.length = half_width * half_height; + frame->u_plane.data = new uint8[frame->u_plane.length]; + + frame->v_plane.stride = half_width; + frame->v_plane.length = half_width * half_height; + frame->v_plane.data = new uint8[frame->v_plane.length]; + + memcpy(frame->y_plane.data, raw_data, frame->width * frame->height); + memcpy(frame->u_plane.data, raw_data + frame->width * frame->height, + half_width * half_height); + memcpy(frame->v_plane.data, raw_data + frame->width * frame->height + + half_width * half_height, half_width * half_height); + delete [] raw_data; + return true; +} + } // namespace cast } // namespace media diff --git a/media/cast/test/video_utility.h b/media/cast/test/video_utility.h index 547b72d..33efa86 100644 --- a/media/cast/test/video_utility.h +++ b/media/cast/test/video_utility.h @@ -17,5 +17,9 @@ double I420PSNR(const I420VideoFrame& frame1, const I420VideoFrame& frame2); // Memory is allocated within the function. void PopulateVideoFrame(I420VideoFrame* frame, int start_value); +// Populate a video frame from a file. +// Returns true if frame was populated, false if not (EOF). +bool PopulateVideoFrameFromFile(I420VideoFrame* frame, FILE* video_file); + } // namespace cast } // namespace media diff --git a/media/cast/video_sender/video_sender.cc b/media/cast/video_sender/video_sender.cc index 966fe93..7d8f7c1 100644 --- a/media/cast/video_sender/video_sender.cc +++ b/media/cast/video_sender/video_sender.cc @@ -117,7 +117,6 @@ void VideoSender::InsertRawVideoFrame( if (!video_encoder_->EncodeVideoFrame(video_frame, capture_time, base::Bind(&VideoSender::SendEncodedVideoFrameMainThread, weak_factory_.GetWeakPtr()), callback)) { - VLOG(0) << "Failed to InsertRawVideoFrame"; cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); } } |