普通文本  |  366行  |  12.57 KB

// 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"

namespace media {
namespace cast {
// Settings chosen to match default receiver settings.
#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 {
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. This guarantees that the audio_bus is valid for
// the entire duration of the encode/send process (not equivalent to DoNothing).
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(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<base::TaskRunner> thread_proxy,
              base::TickClock* clock,
              const VideoSenderConfig& video_config,
              FrameInput* frame_input)
      : test_app_thread_proxy_(thread_proxy),
        video_config_(video_config),
        audio_diff_(kFrameTimerMs),
        frame_input_(frame_input),
        synthetic_count_(0),
        clock_(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 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)));

    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) {
      VLOG(1) << "Wait" <<
          (video_frame_time - elapsed_time).InMilliseconds();
     test_app_thread_proxy_->PostDelayedTask(FROM_HERE,
        base::Bind(&SendProcess::SendVideoFrameOnTime, base::Unretained(this),
                   video_frame),
        video_frame_time - elapsed_time);
    } else {
      test_app_thread_proxy_->PostTask(FROM_HERE,
      base::Bind(&SendProcess::SendVideoFrameOnTime, base::Unretained(this),
                 video_frame));
    }
  }

  void SendVideoFrameOnTime(scoped_refptr<media::VideoFrame> video_frame) {
    send_time_ = clock_->NowTicks();
    frame_input_->InsertRawVideoFrame(video_frame, send_time_);
    test_app_thread_proxy_->PostTask(FROM_HERE,
          base::Bind(&SendProcess::SendFrame, base::Unretained(this)));
  }

 private:
  scoped_refptr<base::TaskRunner> test_app_thread_proxy_;
  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 test_thread("Cast sender test app thread");
  base::Thread main_thread("Cast main send thread");
  base::Thread audio_thread("Cast audio encoder thread");
  base::Thread video_thread("Cast video encoder thread");
  test_thread.Start();
  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(test_thread.message_loop_proxy(),
                               cast_environment->Clock(),
                               video_config,
                               frame_input));

  send_process->SendFrame();
  io_message_loop.Run();
  transport->StopReceiving();
  return 0;
}