blob: 2fae254d7e1fd3fe11539c5f186f59f000bbde7e [file] [log] [blame]
/*
* libjingle
* Copyright 2004--2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_WEBRTC_VIDEO
#include "talk/session/phone/webrtcvideoengine.h"
#include "talk/base/basictypes.h"
#include "talk/base/common.h"
#include "talk/base/buffer.h"
#include "talk/base/byteorder.h"
#include "talk/base/logging.h"
#include "talk/base/stringutils.h"
#include "talk/session/phone/rtputils.h"
#include "talk/session/phone/videorenderer.h"
#include "talk/session/phone/webrtcpassthroughrender.h"
#include "talk/session/phone/webrtcvoiceengine.h"
#include "talk/session/phone/webrtcvideocapturer.h"
#include "talk/session/phone/webrtcvideoframe.h"
#include "talk/session/phone/webrtcvie.h"
#include "talk/session/phone/webrtcvoe.h"
// TODO Change video protection calls when WebRTC API has changed.
#define WEBRTC_VIDEO_AVPF_NACK_ONLY
namespace cricket {
static const int kDefaultLogSeverity = talk_base::LS_WARNING;
static const int kMinVideoBitrate = 100;
static const int kStartVideoBitrate = 300;
static const int kMaxVideoBitrate = 2000;
static const int kVideoMtu = 1200;
static const int kVideoRtpBufferSize = 65536;
static const char kVp8PayloadName[] = "VP8";
static const char kRedPayloadName[] = "red";
static const char kFecPayloadName[] = "ulpfec";
static const int kDefaultNumberOfTemporalLayers = 3;
static void LogMultiline(talk_base::LoggingSeverity sev, char* text) {
const char* delim = "\r\n";
for (char* tok = strtok(text, delim); tok; tok = strtok(NULL, delim)) {
LOG_V(sev) << tok;
}
}
class WebRtcRenderAdapter : public webrtc::ExternalRenderer {
public:
explicit WebRtcRenderAdapter(VideoRenderer* renderer)
: renderer_(renderer), width_(0), height_(0) {
}
virtual ~WebRtcRenderAdapter() {
}
void SetRenderer(VideoRenderer* renderer) {
talk_base::CritScope cs(&crit_);
renderer_ = renderer;
}
// Implementation of webrtc::ExternalRenderer.
virtual int FrameSizeChange(unsigned int width, unsigned int height,
unsigned int /*number_of_streams*/) {
talk_base::CritScope cs(&crit_);
if (renderer_ == NULL) {
return 0;
}
LOG(LS_INFO) << "WebRtcRenderAdapter frame size changed to: "
<< width << "x" << height;
width_ = width;
height_ = height;
return renderer_->SetSize(width_, height_, 0) ? 0 : -1;
}
virtual int DeliverFrame(unsigned char* buffer, int buffer_size,
unsigned int time_stamp) {
talk_base::CritScope cs(&crit_);
frame_rate_tracker_.Update(1);
if (renderer_ == NULL) {
return 0;
}
WebRtcVideoFrame video_frame;
video_frame.Attach(buffer, buffer_size, width_, height_,
1, 1, 0, time_stamp, 0);
// Sanity check on decoded frame size.
if (buffer_size != static_cast<int>(VideoFrame::SizeOf(width_, height_))) {
LOG(LS_WARNING) << "WebRtcRenderAdapter received a strange frame size: "
<< buffer_size;
}
int ret = renderer_->RenderFrame(&video_frame) ? 0 : -1;
uint8* buffer_temp;
size_t buffer_size_temp;
video_frame.Detach(&buffer_temp, &buffer_size_temp);
return ret;
}
unsigned int width() {
talk_base::CritScope cs(&crit_);
return width_;
}
unsigned int height() {
talk_base::CritScope cs(&crit_);
return height_;
}
int framerate() {
talk_base::CritScope cs(&crit_);
return frame_rate_tracker_.units_second();
}
VideoRenderer* renderer() {
talk_base::CritScope cs(&crit_);
return renderer_;
}
private:
talk_base::CriticalSection crit_;
VideoRenderer* renderer_;
unsigned int width_;
unsigned int height_;
talk_base::RateTracker frame_rate_tracker_;
};
class WebRtcDecoderObserver : public webrtc::ViEDecoderObserver {
public:
explicit WebRtcDecoderObserver(int video_channel)
: video_channel_(video_channel),
framerate_(0),
bitrate_(0),
firs_requested_(0) {
}
// virtual functions from VieDecoderObserver.
virtual void IncomingCodecChanged(const int videoChannel,
const webrtc::VideoCodec& videoCodec) {}
virtual void IncomingRate(const int videoChannel,
const unsigned int framerate,
const unsigned int bitrate) {
ASSERT(video_channel_ == videoChannel);
framerate_ = framerate;
bitrate_ = bitrate;
}
virtual void RequestNewKeyFrame(const int videoChannel) {
ASSERT(video_channel_ == videoChannel);
++firs_requested_;
}
int framerate() const { return framerate_; }
int bitrate() const { return bitrate_; }
int firs_requested() const { return firs_requested_; }
private:
int video_channel_;
int framerate_;
int bitrate_;
int firs_requested_;
};
class WebRtcEncoderObserver : public webrtc::ViEEncoderObserver {
public:
explicit WebRtcEncoderObserver(int video_channel)
: video_channel_(video_channel),
framerate_(0),
bitrate_(0) {
}
// virtual functions from VieEncoderObserver.
virtual void OutgoingRate(const int videoChannel,
const unsigned int framerate,
const unsigned int bitrate) {
ASSERT(video_channel_ == videoChannel);
framerate_ = framerate;
bitrate_ = bitrate;
}
int framerate() const { return framerate_; }
int bitrate() const { return bitrate_; }
private:
int video_channel_;
int framerate_;
int bitrate_;
};
class WebRtcLocalStreamInfo {
public:
int width() {
talk_base::CritScope cs(&crit_);
return width_;
}
int height() {
talk_base::CritScope cs(&crit_);
return height_;
}
int framerate() {
talk_base::CritScope cs(&crit_);
return rate_tracker_.units_second();
}
void UpdateFrame(int width, int height) {
talk_base::CritScope cs(&crit_);
width_ = width;
height_ = height;
rate_tracker_.Update(1);
}
private:
talk_base::CriticalSection crit_;
unsigned int width_;
unsigned int height_;
talk_base::RateTracker rate_tracker_;
};
// WebRtcVideoChannelInfo is a container class with members such as renderer
// and a decoder observer that is used by receive channels.
// It must exist as long as the receive channel is connected to renderer or a
// decoder observer in this class and methods in the class should only be called
// from the worker thread.
class WebRtcVideoChannelInfo {
public:
explicit WebRtcVideoChannelInfo(int channel_id)
: channel_id_(channel_id),
renderer_(NULL),
decoder_observer_(channel_id) {
}
int channel_id() { return channel_id_; }
void SetRenderer(VideoRenderer* renderer) {
renderer_.SetRenderer(renderer);
}
WebRtcRenderAdapter* render_adapter() { return &renderer_; }
WebRtcDecoderObserver* decoder_observer() { return &decoder_observer_; }
private:
int channel_id_; // Webrtc video channel number.
// Renderer for this channel.
WebRtcRenderAdapter renderer_;
WebRtcDecoderObserver decoder_observer_;
};
const WebRtcVideoEngine::VideoCodecPref
WebRtcVideoEngine::kVideoCodecPrefs[] = {
{kVp8PayloadName, 100, 0},
#ifndef WEBRTC_VIDEO_AVPF_NACK_ONLY
{kRedPayloadName, 101, 1},
{kFecPayloadName, 102, 2},
#endif
};
static const int64 kNsPerFrame = 33333333; // 30fps
// The formats are sorted by the descending order of width. We use the order to
// find the next format for CPU and bandwidth adaptation.
const VideoFormatPod WebRtcVideoEngine::kVideoFormats[] = {
{1280, 800, kNsPerFrame, FOURCC_ANY},
{1280, 720, kNsPerFrame, FOURCC_ANY},
{960, 600, kNsPerFrame, FOURCC_ANY},
{960, 540, kNsPerFrame, FOURCC_ANY},
{640, 400, kNsPerFrame, FOURCC_ANY},
{640, 360, kNsPerFrame, FOURCC_ANY},
{640, 480, kNsPerFrame, FOURCC_ANY},
{480, 300, kNsPerFrame, FOURCC_ANY},
{480, 270, kNsPerFrame, FOURCC_ANY},
{480, 360, kNsPerFrame, FOURCC_ANY},
{320, 200, kNsPerFrame, FOURCC_ANY},
{320, 180, kNsPerFrame, FOURCC_ANY},
{320, 240, kNsPerFrame, FOURCC_ANY},
{240, 150, kNsPerFrame, FOURCC_ANY},
{240, 135, kNsPerFrame, FOURCC_ANY},
{240, 180, kNsPerFrame, FOURCC_ANY},
{160, 100, kNsPerFrame, FOURCC_ANY},
{160, 90, kNsPerFrame, FOURCC_ANY},
{160, 120, kNsPerFrame, FOURCC_ANY},
};
const VideoFormatPod WebRtcVideoEngine::kDefaultVideoFormat =
{640, 400, kNsPerFrame, FOURCC_ANY};
WebRtcVideoEngine::WebRtcVideoEngine() {
Construct(new ViEWrapper(), new ViETraceWrapper(), NULL);
}
WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
ViEWrapper* vie_wrapper) {
Construct(vie_wrapper, new ViETraceWrapper(), voice_engine);
}
WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
ViEWrapper* vie_wrapper,
ViETraceWrapper* tracing) {
Construct(vie_wrapper, tracing, voice_engine);
}
void WebRtcVideoEngine::Construct(ViEWrapper* vie_wrapper,
ViETraceWrapper* tracing,
WebRtcVoiceEngine* voice_engine) {
LOG(LS_INFO) << "WebRtcVideoEngine::WebRtcVideoEngine";
vie_wrapper_.reset(vie_wrapper);
vie_wrapper_base_initialized_ = false;
tracing_.reset(tracing);
voice_engine_ = voice_engine;
initialized_ = false;
log_level_ = kDefaultLogSeverity;
render_module_.reset(new WebRtcPassthroughRender());
local_renderer_w_ = local_renderer_h_ = 0;
local_renderer_ = NULL;
owns_capturer_ = false;
video_capturer_ = NULL;
capture_started_ = false;
ApplyLogging();
if (tracing_->SetTraceCallback(this) != 0) {
LOG_RTCERR1(SetTraceCallback, this);
}
// Set default quality levels for our supported codecs. We override them here
// if we know your cpu performance is low, and they can be updated explicitly
// by calling SetDefaultCodec. For example by a flute preference setting, or
// by the server with a jec in response to our reported system info.
VideoCodec max_codec(kVideoCodecPrefs[0].payload_type,
kVideoCodecPrefs[0].name,
kDefaultVideoFormat.width,
kDefaultVideoFormat.height,
VideoFormat::IntervalToFps(kDefaultVideoFormat.interval),
0);
if (!SetDefaultCodec(max_codec)) {
LOG(LS_ERROR) << "Failed to initialize list of supported codec types";
}
}
WebRtcVideoEngine::~WebRtcVideoEngine() {
ClearCapturer();
LOG(LS_INFO) << "WebRtcVideoEngine::~WebRtcVideoEngine";
if (initialized_) {
Terminate();
}
tracing_->SetTraceCallback(NULL);
}
bool WebRtcVideoEngine::Init() {
LOG(LS_INFO) << "WebRtcVideoEngine::Init";
bool result = InitVideoEngine();
if (result) {
LOG(LS_INFO) << "VideoEngine Init done";
} else {
LOG(LS_ERROR) << "VideoEngine Init failed, releasing";
Terminate();
}
return result;
}
bool WebRtcVideoEngine::InitVideoEngine() {
LOG(LS_INFO) << "WebRtcVideoEngine::InitVideoEngine";
// Init WebRTC VideoEngine.
if (!vie_wrapper_base_initialized_) {
if (vie_wrapper_->base()->Init() != 0) {
LOG_RTCERR0(Init);
return false;
}
vie_wrapper_base_initialized_ = true;
}
// Log the VoiceEngine version info.
char buffer[1024] = "";
if (vie_wrapper_->base()->GetVersion(buffer) != 0) {
LOG_RTCERR0(GetVersion);
return false;
}
LOG(LS_INFO) << "WebRtc VideoEngine Version:";
LogMultiline(talk_base::LS_INFO, buffer);
// Hook up to VoiceEngine for sync purposes, if supplied.
if (!voice_engine_) {
LOG(LS_WARNING) << "NULL voice engine";
} else if ((vie_wrapper_->base()->SetVoiceEngine(
voice_engine_->voe()->engine())) != 0) {
LOG_RTCERR0(SetVoiceEngine);
return false;
}
// Register for callbacks from the engine.
if ((vie_wrapper_->base()->RegisterObserver(*this)) != 0) {
LOG_RTCERR0(RegisterObserver);
return false;
}
// Register our custom render module.
if (vie_wrapper_->render()->RegisterVideoRenderModule(
*render_module_.get()) != 0) {
LOG_RTCERR0(RegisterVideoRenderModule);
return false;
}
initialized_ = true;
return true;
}
void WebRtcVideoEngine::Terminate() {
LOG(LS_INFO) << "WebRtcVideoEngine::Terminate";
initialized_ = false;
SetCapture(false);
if (vie_wrapper_->render()->DeRegisterVideoRenderModule(
*render_module_.get()) != 0) {
LOG_RTCERR0(DeRegisterVideoRenderModule);
}
if (vie_wrapper_->base()->DeregisterObserver() != 0) {
LOG_RTCERR0(DeregisterObserver);
}
if (vie_wrapper_->base()->SetVoiceEngine(NULL) != 0) {
LOG_RTCERR0(SetVoiceEngine);
}
}
int WebRtcVideoEngine::GetCapabilities() {
return VIDEO_RECV | VIDEO_SEND;
}
bool WebRtcVideoEngine::SetOptions(int options) {
return true;
}
bool WebRtcVideoEngine::SetDefaultEncoderConfig(
const VideoEncoderConfig& config) {
return SetDefaultCodec(config.max_codec);
}
// SetDefaultCodec may be called while the capturer is running. For example, a
// test call is started in a page with QVGA default codec, and then a real call
// is started in another page with VGA default codec. This is the corner case
// and happens only when a session is started. We ignore this case currently.
bool WebRtcVideoEngine::SetDefaultCodec(const VideoCodec& codec) {
if (!RebuildCodecList(codec)) {
LOG(LS_WARNING) << "Failed to RebuildCodecList";
return false;
}
default_codec_format_ = VideoFormat(
video_codecs_[0].width,
video_codecs_[0].height,
VideoFormat::FpsToInterval(video_codecs_[0].framerate),
FOURCC_ANY);
return true;
}
WebRtcVideoMediaChannel* WebRtcVideoEngine::CreateChannel(
VoiceMediaChannel* voice_channel) {
WebRtcVideoMediaChannel* channel =
new WebRtcVideoMediaChannel(this, voice_channel);
if (!channel->Init()) {
delete channel;
channel = NULL;
}
return channel;
}
bool WebRtcVideoEngine::SetCaptureDevice(const Device* device) {
const bool owns_capturer = true;
if (!device) {
if (!SetCapturer(NULL, owns_capturer)) {
return false;
}
LOG(LS_INFO) << "Camera set to NULL";
return true;
}
// No-op if the device hasn't changed.
if ((video_capturer_ != NULL) && video_capturer_->GetId() == device->id) {
return true;
}
// Create a new capturer for the specified device.
VideoCapturer* capturer = CreateVideoCapturer(*device);
if (!capturer) {
LOG(LS_ERROR) << "Failed to create camera '" << device->name << "', id='"
<< device->id << "'";
return false;
}
if (!SetCapturer(capturer, owns_capturer)) {
return false;
}
LOG(LS_INFO) << "Camera set to '" << device->name << "', id='"
<< device->id << "'";
return true;
}
bool WebRtcVideoEngine::SetCaptureModule(webrtc::VideoCaptureModule* vcm) {
const bool owns_capturer = true;
if (!vcm) {
if (!SetCapturer(NULL, owns_capturer)) {
return false;
}
LOG(LS_INFO) << "Camera set to NULL";
return true;
}
// Create a new capturer for the specified device.
WebRtcVideoCapturer* capturer = new WebRtcVideoCapturer;
if (!capturer->Init(vcm)) {
LOG(LS_ERROR) << "Failed to create camera from VCM";
delete capturer;
return false;
}
if (!SetCapturer(capturer, owns_capturer)) {
return false;
}
LOG(LS_INFO) << "Camera created with VCM";
return true;
}
bool WebRtcVideoEngine::SetVideoCapturer(VideoCapturer* capturer,
uint32 /*ssrc*/) {
const bool owns_capturer = false;
return SetCapturer(capturer, owns_capturer);
}
bool WebRtcVideoEngine::SetLocalRenderer(VideoRenderer* renderer) {
local_renderer_w_ = local_renderer_h_ = 0;
local_renderer_ = renderer;
return true;
}
CaptureResult WebRtcVideoEngine::SetCapture(bool capture) {
bool old_capture = capture_started_;
capture_started_ = capture;
CaptureResult res = UpdateCapturingState();
if (res != CR_SUCCESS && res != CR_PENDING) {
capture_started_ = old_capture;
}
return res;
}
VideoCapturer* WebRtcVideoEngine::CreateVideoCapturer(const Device& device) {
WebRtcVideoCapturer* capturer = new WebRtcVideoCapturer;
if (!capturer->Init(device)) {
delete capturer;
return NULL;
}
return capturer;
}
CaptureResult WebRtcVideoEngine::UpdateCapturingState() {
CaptureResult result = CR_SUCCESS;
bool capture = capture_started_;
if (!IsCapturing() && capture) { // Start capturing.
if (video_capturer_ == NULL) {
return CR_NO_DEVICE;
}
VideoFormat capture_format;
if (!video_capturer_->GetBestCaptureFormat(default_codec_format_,
&capture_format)) {
LOG(LS_WARNING) << "Unsupported format:"
<< " width=" << default_codec_format_.width
<< " height=" << default_codec_format_.height
<< ". Supported formats are:";
const std::vector<VideoFormat>* formats =
video_capturer_->GetSupportedFormats();
if (formats) {
for (std::vector<VideoFormat>::const_iterator i = formats->begin();
i != formats->end(); ++i) {
const VideoFormat& format = *i;
LOG(LS_WARNING) << " " << GetFourccName(format.fourcc) << ":"
<< format.width << "x" << format.height << "x"
<< format.framerate();
}
}
return CR_FAILURE;
}
// Start the video capturer.
result = video_capturer_->Start(capture_format);
if (CR_SUCCESS != result && CR_PENDING != result) {
LOG(LS_ERROR) << "Failed to start the video capturer";
return result;
}
} else if (IsCapturing() && !capture) { // Stop capturing.
video_capturer_->Stop();
}
return result;
}
bool WebRtcVideoEngine::IsCapturing() const {
return (video_capturer_ != NULL) && video_capturer_->IsRunning();
}
void WebRtcVideoEngine::OnFrameCaptured(VideoCapturer* capturer,
const CapturedFrame* frame) {
// Force 16:10 for now. We'll be smarter with the capture refactor.
int cropped_height = frame->width * default_codec_format_.height
/ default_codec_format_.width;
if (cropped_height > frame->height) {
// TODO: Once we support horizontal cropping, add cropped_width.
cropped_height = frame->height;
}
// This CapturedFrame* will already be in I420. In the future, when
// WebRtcVideoFrame has support for independent planes, we can just attach
// to it and update the pointers when cropping.
WebRtcVideoFrame i420_frame;
if (!i420_frame.Init(frame, frame->width, cropped_height)) {
LOG(LS_ERROR) << "Couldn't convert to I420! "
<< frame->width << " x " << cropped_height;
return;
}
// Send I420 frame to the local renderer.
if (local_renderer_) {
if (local_renderer_w_ != static_cast<int>(i420_frame.GetWidth()) ||
local_renderer_h_ != static_cast<int>(i420_frame.GetHeight())) {
local_renderer_->SetSize(local_renderer_w_ = i420_frame.GetWidth(),
local_renderer_h_ = i420_frame.GetHeight(), 0);
}
local_renderer_->RenderFrame(&i420_frame);
}
// Send I420 frame to the registered senders.
talk_base::CritScope cs(&channels_crit_);
for (VideoChannels::iterator it = channels_.begin();
it != channels_.end(); ++it) {
if ((*it)->sending()) (*it)->SendFrame(0, &i420_frame);
}
}
const std::vector<VideoCodec>& WebRtcVideoEngine::codecs() const {
return video_codecs_;
}
void WebRtcVideoEngine::SetLogging(int min_sev, const char* filter) {
log_level_ = min_sev;
ApplyLogging();
}
int WebRtcVideoEngine::GetLastEngineError() {
return vie_wrapper_->error();
}
// Checks to see whether we comprehend and could receive a particular codec
bool WebRtcVideoEngine::FindCodec(const VideoCodec& in) {
for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) {
const VideoFormat fmt(kVideoFormats[i]);
if ((in.width == 0 && in.height == 0) ||
(fmt.width == in.width && fmt.height == in.height)) {
for (int j = 0; j < ARRAY_SIZE(kVideoCodecPrefs); ++j) {
VideoCodec codec(kVideoCodecPrefs[j].payload_type,
kVideoCodecPrefs[j].name, 0, 0, 0, 0);
if (codec.Matches(in)) {
return true;
}
}
}
}
return false;
}
// Given the requested codec, returns true if we can send that codec type and
// updates out with the best quality we could send for that codec. If current is
// not empty, we constrain out so that its aspect ratio matches current's.
bool WebRtcVideoEngine::CanSendCodec(const VideoCodec& requested,
const VideoCodec& current,
VideoCodec* out) {
if (!out) {
return false;
}
std::vector<VideoCodec>::const_iterator local_max;
for (local_max = video_codecs_.begin();
local_max < video_codecs_.end();
++local_max) {
// First match codecs by payload type
if (!requested.Matches(local_max->id, local_max->name)) {
continue;
}
out->id = requested.id;
out->name = requested.name;
out->preference = requested.preference;
out->framerate = talk_base::_min(requested.framerate, local_max->framerate);
out->width = 0;
out->height = 0;
if (0 == requested.width && 0 == requested.height) {
// Special case with resolution 0. The channel should not send frames.
return true;
} else if (0 == requested.width || 0 == requested.height) {
// 0xn and nx0 are invalid resolutions.
return false;
}
// Pick the best quality that is within their and our bounds and has the
// correct aspect ratio.
for (int j = 0; j < ARRAY_SIZE(kVideoFormats); ++j) {
const VideoFormat format(kVideoFormats[j]);
// Skip any format that is larger than the local or remote maximums, or
// smaller than the current best match
if (format.width > requested.width || format.height > requested.height ||
format.width > local_max->width ||
(format.width < out->width && format.height < out->height)) {
continue;
}
bool better = false;
// Check any further constraints on this prospective format
if (!out->width || !out->height) {
// If we don't have any matches yet, this is the best so far.
better = true;
} else if (current.width && current.height) {
// current is set so format must match its ratio exactly.
better =
(format.width * current.height == format.height * current.width);
} else {
// Prefer closer aspect ratios i.e
// format.aspect - requested.aspect < out.aspect - requested.aspect
better = abs(format.width * requested.height * out->height -
requested.width * format.height * out->height) <
abs(out->width * format.height * requested.height -
requested.width * format.height * out->height);
}
if (better) {
out->width = format.width;
out->height = format.height;
}
}
if (out->width > 0) {
return true;
}
}
return false;
}
void WebRtcVideoEngine::ConvertToCricketVideoCodec(
const webrtc::VideoCodec& in_codec, VideoCodec& out_codec) {
out_codec.id = in_codec.plType;
out_codec.name = in_codec.plName;
out_codec.width = in_codec.width;
out_codec.height = in_codec.height;
out_codec.framerate = in_codec.maxFramerate;
}
bool WebRtcVideoEngine::ConvertFromCricketVideoCodec(
const VideoCodec& in_codec, webrtc::VideoCodec& out_codec) {
bool found = false;
int ncodecs = vie_wrapper_->codec()->NumberOfCodecs();
for (int i = 0; i < ncodecs; ++i) {
if (vie_wrapper_->codec()->GetCodec(i, out_codec) == 0 &&
in_codec.name == out_codec.plName) {
found = true;
break;
}
}
if (!found) {
LOG(LS_ERROR) << "invalid codec type";
return false;
}
if (in_codec.id != 0)
out_codec.plType = in_codec.id;
if (in_codec.width != 0)
out_codec.width = in_codec.width;
if (in_codec.height != 0)
out_codec.height = in_codec.height;
if (in_codec.framerate != 0)
out_codec.maxFramerate = in_codec.framerate;
// Init the codec with the default bandwidth options.
out_codec.minBitrate = kMinVideoBitrate;
out_codec.startBitrate = kStartVideoBitrate;
out_codec.maxBitrate = kMaxVideoBitrate;
return true;
}
void WebRtcVideoEngine::RegisterChannel(WebRtcVideoMediaChannel *channel) {
talk_base::CritScope cs(&channels_crit_);
channels_.push_back(channel);
}
void WebRtcVideoEngine::UnregisterChannel(WebRtcVideoMediaChannel *channel) {
talk_base::CritScope cs(&channels_crit_);
channels_.erase(std::remove(channels_.begin(), channels_.end(), channel),
channels_.end());
}
bool WebRtcVideoEngine::SetVoiceEngine(WebRtcVoiceEngine* voice_engine) {
if (initialized_) {
LOG(LS_WARNING) << "SetVoiceEngine can not be called after Init.";
return false;
}
voice_engine_ = voice_engine;
return true;
}
bool WebRtcVideoEngine::EnableTimedRender() {
if (initialized_) {
LOG(LS_WARNING) << "EnableTimedRender can not be called after Init.";
return false;
}
render_module_.reset(webrtc::VideoRender::CreateVideoRender(0, NULL,
false, webrtc::kRenderExternal));
return true;
}
void WebRtcVideoEngine::ApplyLogging() {
int filter = 0;
switch (log_level_) {
case talk_base::LS_VERBOSE: filter |= webrtc::kTraceAll;
case talk_base::LS_INFO: filter |= webrtc::kTraceStateInfo;
case talk_base::LS_WARNING: filter |= webrtc::kTraceWarning;
case talk_base::LS_ERROR: filter |=
webrtc::kTraceError | webrtc::kTraceCritical;
}
tracing_->SetTraceFilter(filter);
}
// Rebuilds the codec list to be only those that are less intensive
// than the specified codec.
bool WebRtcVideoEngine::RebuildCodecList(const VideoCodec& in_codec) {
if (!FindCodec(in_codec))
return false;
video_codecs_.clear();
bool found = false;
for (size_t i = 0; i < ARRAY_SIZE(kVideoCodecPrefs); ++i) {
const VideoCodecPref& pref(kVideoCodecPrefs[i]);
if (!found)
found = (in_codec.name == pref.name);
if (found) {
VideoCodec codec(pref.payload_type, pref.name,
in_codec.width, in_codec.height, in_codec.framerate,
ARRAY_SIZE(kVideoCodecPrefs) - i);
video_codecs_.push_back(codec);
}
}
ASSERT(found);
return true;
}
bool WebRtcVideoEngine::SetCapturer(VideoCapturer* capturer,
bool own_capturer) {
if (capturer == NULL) {
// Stop capturing before clearing the capturer.
if (SetCapture(false) != CR_SUCCESS) {
LOG(LS_WARNING) << "Camera failed to stop.";
return false;
}
ClearCapturer();
return true;
}
// Hook up signals and install the supplied capturer.
SignalCaptureResult.repeat(capturer->SignalStartResult);
capturer->SignalFrameCaptured.connect(this,
&WebRtcVideoEngine::OnFrameCaptured);
ClearCapturer();
video_capturer_ = capturer;
owns_capturer_ = own_capturer;
// Possibly restart the capturer if it is supposed to be running.
CaptureResult result = UpdateCapturingState();
if (result != CR_SUCCESS && result != CR_PENDING) {
LOG(LS_WARNING) << "Camera failed to restart";
return false;
}
return true;
}
void WebRtcVideoEngine::PerformanceAlarm(const unsigned int cpu_load) {
LOG(LS_INFO) << "WebRtcVideoEngine::PerformanceAlarm";
}
// Ignore spammy trace messages, mostly from the stats API when we haven't
// gotten RTCP info yet from the remote side.
bool WebRtcVideoEngine::ShouldIgnoreTrace(const std::string& trace) {
static const char* const kTracesToIgnore[] = {
NULL
};
for (const char* const* p = kTracesToIgnore; *p; ++p) {
if (trace.find(*p) == 0) {
return true;
}
}
return false;
}
int WebRtcVideoEngine::GetNumOfChannels() {
talk_base::CritScope cs(&channels_crit_);
return channels_.size();
}
void WebRtcVideoEngine::Print(const webrtc::TraceLevel level,
const char* trace, const int length) {
talk_base::LoggingSeverity sev = talk_base::LS_VERBOSE;
if (level == webrtc::kTraceError || level == webrtc::kTraceCritical)
sev = talk_base::LS_ERROR;
else if (level == webrtc::kTraceWarning)
sev = talk_base::LS_WARNING;
else if (level == webrtc::kTraceStateInfo || level == webrtc::kTraceInfo)
sev = talk_base::LS_INFO;
if (sev >= log_level_) {
// Skip past boilerplate prefix text
if (length < 72) {
std::string msg(trace, length);
LOG(LS_ERROR) << "Malformed webrtc log message: ";
LOG_V(sev) << msg;
} else {
std::string msg(trace + 71, length - 72);
if (!ShouldIgnoreTrace(msg) &&
(!voice_engine_ || !voice_engine_->ShouldIgnoreTrace(msg))) {
LOG_V(sev) << "WebRtc:" << msg;
}
}
}
}
// TODO: stubs for now
bool WebRtcVideoEngine::RegisterProcessor(
VideoProcessor* video_processor) {
return true;
}
bool WebRtcVideoEngine::UnregisterProcessor(
VideoProcessor* video_processor) {
return true;
}
void WebRtcVideoEngine::ClearCapturer() {
if (owns_capturer_) {
delete video_capturer_;
}
video_capturer_ = NULL;
}
// WebRtcVideoMediaChannel
WebRtcVideoMediaChannel::WebRtcVideoMediaChannel(
WebRtcVideoEngine* engine, VoiceMediaChannel* channel)
: engine_(engine),
voice_channel_(channel),
vie_channel_(-1),
vie_capture_(-1),
external_capture_(NULL),
sending_(false),
render_started_(false),
muted_(false),
send_min_bitrate_(kMinVideoBitrate),
send_start_bitrate_(kStartVideoBitrate),
send_max_bitrate_(kMaxVideoBitrate),
local_stream_info_(new WebRtcLocalStreamInfo()),
channel_options_(0) {
engine->RegisterChannel(this);
}
bool WebRtcVideoMediaChannel::Init() {
if (engine_->vie()->base()->CreateChannel(vie_channel_) != 0) {
LOG_RTCERR1(CreateChannel, vie_channel_);
return false;
}
if (!ConfigureChannel(vie_channel_)) {
engine_->vie()->base()->DeleteChannel(vie_channel_);
vie_channel_ = -1;
return false;
}
if (!ConfigureReceiving(vie_channel_, 0)) {
engine_->vie()->base()->DeleteChannel(vie_channel_);
vie_channel_ = -1;
return false;
}
LOG(LS_INFO) << "WebRtcVideoMediaChannel::Init "
<< "vie_channel " << vie_channel_ << " created";
// Register external capture.
if (engine()->vie()->capture()->AllocateExternalCaptureDevice(
vie_capture_, external_capture_) != 0) {
LOG_RTCERR0(AllocateExternalCaptureDevice);
return false;
}
// Connect external capture.
if (engine()->vie()->capture()->ConnectCaptureDevice(
vie_capture_, vie_channel_) != 0) {
LOG_RTCERR2(ConnectCaptureDevice, vie_capture_, vie_channel_);
return false;
}
// Register encoder observer for outgoing framerate and bitrate.
encoder_observer_.reset(new WebRtcEncoderObserver(vie_channel_));
if (engine()->vie()->codec()->RegisterEncoderObserver(
vie_channel_, *encoder_observer_) != 0) {
LOG_RTCERR1(RegisterEncoderObserver, encoder_observer_.get());
return false;
}
return true;
}
WebRtcVideoMediaChannel::~WebRtcVideoMediaChannel() {
if (vie_channel_ != -1) {
// Stop sending.
SetSend(false);
if (engine()->vie()->codec()->DeregisterEncoderObserver(
vie_channel_) != 0) {
LOG_RTCERR1(DeregisterEncoderObserver, vie_channel_);
}
// Stop the renderer.
SetRender(false);
// Destroy the external capture interface.
if (vie_capture_ != -1) {
if (engine()->vie()->capture()->DisconnectCaptureDevice(
vie_channel_) != 0) {
LOG_RTCERR1(DisconnectCaptureDevice, vie_channel_);
}
if (engine()->vie()->capture()->ReleaseCaptureDevice(
vie_capture_) != 0) {
LOG_RTCERR1(ReleaseCaptureDevice, vie_capture_);
}
}
// Remove all receive streams and the default channel.
while (!mux_channels_.empty()) {
RemoveStream(mux_channels_.begin()->first);
}
}
// Unregister the channel from the engine.
engine()->UnregisterChannel(this);
}
bool WebRtcVideoMediaChannel::SetRecvCodecs(
const std::vector<VideoCodec>& codecs) {
receive_codecs_.clear();
for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
iter != codecs.end(); ++iter) {
if (engine()->FindCodec(*iter)) {
webrtc::VideoCodec wcodec;
if (engine()->ConvertFromCricketVideoCodec(*iter, wcodec)) {
receive_codecs_.push_back(wcodec);
}
} else {
LOG(LS_INFO) << "Unknown codec " << iter->name;
return false;
}
}
for (ChannelMap::iterator it = mux_channels_.begin();
it != mux_channels_.end(); ++it) {
if (!SetReceiveCodecs(it->second->channel_id()))
return false;
}
return true;
}
bool WebRtcVideoMediaChannel::SetSendCodecs(
const std::vector<VideoCodec>& codecs) {
// Match with local video codec list.
std::vector<webrtc::VideoCodec> send_codecs;
int red_type = -1, fec_type = -1;
VideoCodec checked_codec;
VideoCodec current; // defaults to 0x0
if (sending_) {
engine()->ConvertToCricketVideoCodec(*send_codec_, current);
}
for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
iter != codecs.end(); ++iter) {
if (_stricmp(iter->name.c_str(), kRedPayloadName) == 0) {
red_type = iter->id;
} else if (_stricmp(iter->name.c_str(), kFecPayloadName) == 0) {
fec_type = iter->id;
} else if (engine()->CanSendCodec(*iter, current, &checked_codec)) {
webrtc::VideoCodec wcodec;
if (engine()->ConvertFromCricketVideoCodec(checked_codec, wcodec)) {
send_codecs.push_back(wcodec);
}
} else {
LOG(LS_WARNING) << "Unknown codec " << iter->name;
}
}
// Fail if we don't have a match.
if (send_codecs.empty()) {
LOG(LS_WARNING) << "No matching codecs avilable";
return false;
}
#ifndef WEBRTC_VIDEO_AVPF_NACK_ONLY
// Configure FEC if enabled.
if (!SetNackFec(red_type, fec_type)) {
return false;
}
#endif
// Select the first matched codec.
webrtc::VideoCodec& codec(send_codecs[0]);
// Set the default number of temporal layers for VP8.
if (webrtc::kVideoCodecVP8 == codec.codecType) {
codec.codecSpecific.VP8.numberOfTemporalLayers =
kDefaultNumberOfTemporalLayers;
}
if (!SetSendCodec(
codec, send_min_bitrate_, send_start_bitrate_, send_max_bitrate_)) {
return false;
}
LOG(LS_INFO) << "Selected video codec " << send_codec_->plName << "/"
<< send_codec_->width << "x" << send_codec_->height << "x"
<< static_cast<int>(send_codec_->maxFramerate);
if (webrtc::kVideoCodecVP8 == codec.codecType) {
LOG(LS_INFO) << "VP8 number of layers: "
<< static_cast<int>(
send_codec_->codecSpecific.VP8.numberOfTemporalLayers);
}
return true;
}
bool WebRtcVideoMediaChannel::SetRender(bool render) {
if (render == render_started_) {
return true; // no action required
}
bool ret = true;
for (ChannelMap::iterator it = mux_channels_.begin();
it != mux_channels_.end(); ++it) {
if (render) {
if (engine()->vie()->render()->StartRender(
it->second->channel_id()) != 0) {
LOG_RTCERR1(StartRender, it->second->channel_id());
ret = false;
}
} else {
if (engine()->vie()->render()->StopRender(
it->second->channel_id()) != 0) {
LOG_RTCERR1(StopRender, it->second->channel_id());
ret = false;
}
}
}
if (ret) {
render_started_ = render;
}
return ret;
}
bool WebRtcVideoMediaChannel::SetSend(bool send) {
if (send == sending()) {
return true; // no action required
}
if (send) {
// We've been asked to start sending.
// SetSendCodecs must have been called already.
if (!send_codec_.get()) {
return false;
}
if (engine()->vie()->base()->StartSend(vie_channel_) != 0) {
LOG_RTCERR1(StartSend, vie_channel_);
return false;
}
} else {
// We've been asked to stop sending.
if (engine()->vie()->base()->StopSend(vie_channel_) != 0) {
LOG_RTCERR1(StopSend, vie_channel_);
return false;
}
}
sending_ = send;
return true;
}
int WebRtcVideoMediaChannel::GetChannelNum(uint32 ssrc) {
ChannelMap::iterator it = mux_channels_.find(ssrc);
return (it != mux_channels_.end()) ? it->second->channel_id() : -1;
}
bool WebRtcVideoMediaChannel::AddStream(uint32 ssrc, uint32 voice_ssrc) {
if (mux_channels_.find(ssrc) != mux_channels_.end()) {
return false;
}
// Create a new channel for receiving video data.
// TODO: In order to support REMB we connect all receiving channels
// to our master send channel. This have to be done in a later cl when it have
// been properly implemented in webrtc.
int channel_id = -1;
if (engine_->vie()->base()->CreateChannel(channel_id) != 0) {
LOG_RTCERR1(CreateChannel, channel_id);
return false;
}
if (!ConfigureChannel(channel_id) || !ConfigureReceiving(channel_id, ssrc)) {
engine_->vie()->base()->DeleteChannel(channel_id);
return false;
}
LOG(LS_INFO) << "New video stream " << ssrc
<< " registered to VideoEngine channel #"
<< channel_id << ".";
return true;
}
bool WebRtcVideoMediaChannel::RemoveStream(uint32 ssrc) {
ChannelMap::iterator it = mux_channels_.find(ssrc);
if (it == mux_channels_.end()) {
return false;
}
WebRtcVideoChannelInfo* info = it->second;
int channel_id = info->channel_id();
if (engine()->vie()->render()->RemoveRenderer(channel_id) != 0) {
LOG_RTCERR1(RemoveRenderer, channel_id);
}
if (engine()->vie()->network()->DeregisterSendTransport(channel_id) !=0) {
LOG_RTCERR1(DeRegisterSendTransport, channel_id);
}
if (engine()->vie()->codec()->DeregisterDecoderObserver(
channel_id) != 0) {
LOG_RTCERR1(DeregisterDecoderObserver, channel_id);
}
LOG(LS_INFO) << "Removing video stream " << ssrc
<< " with VideoEngine channel #"
<< channel_id << ".";
if (engine()->vie()->base()->DeleteChannel(channel_id) == -1) {
LOG_RTCERR1(DeleteChannel, channel_id);
// Leak the WebRtcVideoChannelInfo owned by |it| but remove the channel from
// mux_channels_.
mux_channels_.erase(it);
return false;
}
// Delete the WebRtcVideoChannelInfo pointed to by it->second.
delete info;
mux_channels_.erase(it);
return true;
}
bool WebRtcVideoMediaChannel::SetRenderer(uint32 ssrc,
VideoRenderer* renderer) {
if (mux_channels_.find(ssrc) == mux_channels_.end())
return false;
mux_channels_[ssrc]->SetRenderer(renderer);
return true;
}
bool WebRtcVideoMediaChannel::GetStats(VideoMediaInfo* info) {
// Get basic statistics.
unsigned int bytes_sent, packets_sent, bytes_recv, packets_recv;
unsigned int ssrc;
if (engine_->vie()->rtp()->GetRTPStatistics(vie_channel_,
bytes_sent, packets_sent, bytes_recv, packets_recv) != 0) {
LOG_RTCERR1(GetRTPStatistics, vie_channel_);
return false;
}
// Get sender statistics and build VideoSenderInfo.
if (engine_->vie()->rtp()->GetLocalSSRC(vie_channel_, ssrc) == 0) {
VideoSenderInfo sinfo;
sinfo.ssrc = ssrc;
sinfo.codec_name = send_codec_.get() ? send_codec_->plName : "";
sinfo.bytes_sent = bytes_sent;
sinfo.packets_sent = packets_sent;
sinfo.packets_cached = -1;
sinfo.packets_lost = -1;
sinfo.fraction_lost = -1;
sinfo.firs_rcvd = -1;
sinfo.nacks_rcvd = -1;
sinfo.rtt_ms = -1;
sinfo.frame_width = local_stream_info_->width();
sinfo.frame_height = local_stream_info_->height();
sinfo.framerate_input = local_stream_info_->framerate();
sinfo.framerate_sent = encoder_observer_->framerate();
sinfo.nominal_bitrate = encoder_observer_->bitrate();
sinfo.preferred_bitrate = kMaxVideoBitrate;
// Get received RTCP statistics for the sender, if available.
// It's not a fatal error if we can't, since RTCP may not have arrived yet.
uint16 r_fraction_lost;
unsigned int r_cumulative_lost;
unsigned int r_extended_max;
unsigned int r_jitter;
int r_rtt_ms;
if (engine_->vie()->rtp()->GetSentRTCPStatistics(vie_channel_,
r_fraction_lost, r_cumulative_lost, r_extended_max,
r_jitter, r_rtt_ms) == 0) {
// Convert Q8 to float.
sinfo.packets_lost = r_cumulative_lost;
sinfo.fraction_lost = static_cast<float>(r_fraction_lost) / (1 << 8);
sinfo.rtt_ms = r_rtt_ms;
}
info->senders.push_back(sinfo);
} else {
LOG_RTCERR1(GetLocalSSRC, vie_channel_);
}
// Get the SSRC and stats for each receiver, based on our own calculations.
for (ChannelMap::const_iterator it = mux_channels_.begin();
it != mux_channels_.end(); ++it) {
// Don't report receive statistics from the default channel if we have
// specified receive channels.
if (it->first == 0 && mux_channels_.size() > 1)
continue;
WebRtcVideoChannelInfo* channel = it->second;
// Get receiver statistics and build VideoReceiverInfo, if we have data.
if (engine_->vie()->rtp()->GetRemoteSSRC(channel->channel_id(), ssrc) != 0)
continue;
if (engine_->vie()->rtp()->GetRTPStatistics(
channel->channel_id(), bytes_sent, packets_sent, bytes_recv,
packets_recv) != 0) {
LOG_RTCERR1(GetRTPStatistics, channel->channel_id());
return false;
}
VideoReceiverInfo rinfo;
rinfo.ssrc = ssrc;
rinfo.bytes_rcvd = bytes_recv;
rinfo.packets_rcvd = packets_recv;
rinfo.packets_lost = -1;
rinfo.packets_concealed = -1;
rinfo.fraction_lost = -1; // from SentRTCP
rinfo.firs_sent = channel->decoder_observer()->firs_requested();
rinfo.nacks_sent = -1;
rinfo.frame_width = channel->render_adapter()->width();
rinfo.frame_height = channel->render_adapter()->height();
rinfo.framerate_rcvd = channel->decoder_observer()->framerate();
int fps = channel->render_adapter()->framerate();
rinfo.framerate_decoded = fps;
rinfo.framerate_output = fps;
// Get sent RTCP statistics.
uint16 s_fraction_lost;
unsigned int s_cumulative_lost;
unsigned int s_extended_max;
unsigned int s_jitter;
int s_rtt_ms;
if (engine_->vie()->rtp()->GetReceivedRTCPStatistics(channel->channel_id(),
s_fraction_lost, s_cumulative_lost, s_extended_max,
s_jitter, s_rtt_ms) == 0) {
// Convert Q8 to float.
rinfo.packets_lost = s_cumulative_lost;
rinfo.fraction_lost = static_cast<float>(s_fraction_lost) / (1 << 8);
}
info->receivers.push_back(rinfo);
}
// Build BandwidthEstimationInfo.
// TODO: Fill in more BWE stats once we have them.
unsigned int total_bitrate_sent;
unsigned int video_bitrate_sent;
unsigned int fec_bitrate_sent;
unsigned int nack_bitrate_sent;
if (engine_->vie()->rtp()->GetBandwidthUsage(vie_channel_,
total_bitrate_sent, video_bitrate_sent,
fec_bitrate_sent, nack_bitrate_sent) == 0) {
BandwidthEstimationInfo bwe;
bwe.actual_enc_bitrate = video_bitrate_sent;
bwe.transmit_bitrate = total_bitrate_sent;
bwe.retransmit_bitrate = nack_bitrate_sent;
info->bw_estimations.push_back(bwe);
} else {
LOG_RTCERR1(GetBandwidthUsage, vie_channel_);
}
return true;
}
bool WebRtcVideoMediaChannel::SendIntraFrame() {
bool ret = true;
if (engine()->vie()->codec()->SendKeyFrame(vie_channel_) != 0) {
LOG_RTCERR1(SendKeyFrame, vie_channel_);
ret = false;
}
return ret;
}
bool WebRtcVideoMediaChannel::RequestIntraFrame() {
// There is no API exposed to application to request a key frame
// ViE does this internally when there are errors from decoder
return false;
}
void WebRtcVideoMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
// Pick which channel to send this packet to. If this packet doesn't match
// any multiplexed streams, just send it to the default channel. Otherwise,
// send it to the specific decoder instance for that stream.
uint32 ssrc = 0;
if (!GetRtpSsrc(packet->data(), packet->length(), &ssrc))
return;
int which_channel = GetChannelNum(ssrc);
if (which_channel == -1) {
which_channel = video_channel();
}
engine()->vie()->network()->ReceivedRTPPacket(which_channel,
packet->data(),
packet->length());
}
void WebRtcVideoMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
// Sending channels need all RTCP packets with feedback information.
// Even sender reports can contain attached report blocks.
// Receiving channels need sender reports in order to create
// correct receiver reports.
uint32 ssrc = 0;
if (!GetRtcpSsrc(packet->data(), packet->length(), &ssrc)) {
LOG(LS_WARNING) << "Failed to parse SSRC from received RTCP packet.";
return;
}
int type = 0;
if (!GetRtcpType(packet->data(), packet->length(), & type)) {
LOG(LS_WARNING) << "Failed to parse type from received RTCP packet.";
return;
}
// If it is a sender report, find the channel that is listening.
if (type == kRtcpTypeSR) {
int which_channel = GetChannelNum(ssrc);
if (which_channel != -1 && which_channel != vie_channel_) {
engine_->vie()->network()->ReceivedRTCPPacket(which_channel,
packet->data(),
packet->length());
}
}
// The sending channel receives all RTCP packets.
engine_->vie()->network()->ReceivedRTCPPacket(vie_channel_,
packet->data(),
packet->length());
}
void WebRtcVideoMediaChannel::SetSendSsrc(uint32 id) {
if (!sending_) {
if (engine()->vie()->rtp()->SetLocalSSRC(vie_channel_, id) != 0) {
LOG_RTCERR1(SetLocalSSRC, vie_channel_);
}
} else {
LOG(LS_ERROR) << "Channel already in send state";
}
}
bool WebRtcVideoMediaChannel::SetRtcpCName(const std::string& cname) {
if (engine()->vie()->rtp()->SetRTCPCName(vie_channel_,
cname.c_str()) != 0) {
LOG_RTCERR2(SetRTCPCName, vie_channel_, cname.c_str());
return false;
}
return true;
}
bool WebRtcVideoMediaChannel::Mute(bool on) {
muted_ = on;
return true;
}
bool WebRtcVideoMediaChannel::SetSendBandwidth(bool autobw, int bps) {
LOG(LS_INFO) << "RtcVideoMediaChanne::SetSendBandwidth";
if (!send_codec_.get()) {
LOG(LS_INFO) << "The send codec has not been set up yet.";
return true;
}
int min_bitrate;
int start_bitrate;
int max_bitrate;
if (autobw) {
// Use the default values for min bitrate.
min_bitrate = kMinVideoBitrate;
// Use the default value or the bps for the max
max_bitrate = (bps <= 0) ? kMaxVideoBitrate : (bps / 1000);
// Maximum start bitrate can be kStartVideoBitrate.
start_bitrate = talk_base::_min(kStartVideoBitrate, max_bitrate);
} else {
// Use the default start or the bps as the target bitrate.
int target_bitrate = (bps <= 0) ? kStartVideoBitrate : (bps / 1000);
min_bitrate = target_bitrate;
start_bitrate = target_bitrate;
max_bitrate = target_bitrate;
}
if (!SetSendCodec(*send_codec_, min_bitrate, start_bitrate, max_bitrate)) {
return false;
}
return true;
}
bool WebRtcVideoMediaChannel::SetOptions(int options) {
// Always accept options that are unchanged.
if (channel_options_ == options) {
return true;
}
// Reject new options if we're already sending.
if (sending()) {
return false;
}
// Save the options, to be interpreted where appropriate.
channel_options_ = options;
return true;
}
void WebRtcVideoMediaChannel::SetInterface(NetworkInterface* iface) {
MediaChannel::SetInterface(iface);
// Set the RTP recv/send buffer to a bigger size
if (network_interface_) {
network_interface_->SetOption(NetworkInterface::ST_RTP,
talk_base::Socket::OPT_RCVBUF,
kVideoRtpBufferSize);
network_interface_->SetOption(NetworkInterface::ST_RTP,
talk_base::Socket::OPT_SNDBUF,
kVideoRtpBufferSize);
}
}
bool WebRtcVideoMediaChannel::GetRenderer(uint32 ssrc,
VideoRenderer** renderer) {
ChannelMap::const_iterator it = mux_channels_.find(ssrc);
if (it == mux_channels_.end())
return false;
*renderer = it->second->render_adapter()->renderer();
return true;
}
// TODO: Add unittests to test this function.
bool WebRtcVideoMediaChannel::SendFrame(uint32 ssrc, const VideoFrame* frame) {
if (ssrc != 0 || !sending() || !external_capture_) {
return false;
}
// Update local stream statistics.
local_stream_info_->UpdateFrame(frame->GetWidth(), frame->GetHeight());
// Checks if we need to reset vie send codec.
if (!MaybeResetVieSendCodec(frame->GetWidth(), frame->GetHeight(), NULL)) {
LOG(LS_ERROR) << "MaybeResetVieSendCodec failed with "
<< frame->GetWidth() << "x" << frame->GetHeight();
return false;
}
// Blacken the frame if video is muted.
const VideoFrame* frame_out = frame;
talk_base::scoped_ptr<VideoFrame> black_frame;
if (muted_) {
black_frame.reset(frame->Copy());
black_frame->SetToBlack();
frame_out = black_frame.get();
}
webrtc::ViEVideoFrameI420 frame_i420;
// TODO: Update the webrtc::ViEVideoFrameI420
// to use const unsigned char*
frame_i420.y_plane = const_cast<unsigned char*>(frame_out->GetYPlane());
frame_i420.u_plane = const_cast<unsigned char*>(frame_out->GetUPlane());
frame_i420.v_plane = const_cast<unsigned char*>(frame_out->GetVPlane());
frame_i420.y_pitch = frame_out->GetYPitch();
frame_i420.u_pitch = frame_out->GetUPitch();
frame_i420.v_pitch = frame_out->GetVPitch();
frame_i420.width = frame_out->GetWidth();
frame_i420.height = frame_out->GetHeight();
// Convert from nanoseconds to milliseconds.
WebRtc_Word64 clocks = frame_out->GetTimeStamp() /
talk_base::kNumNanosecsPerMillisec;
return (external_capture_->IncomingFrameI420(frame_i420, clocks) == 0);
}
bool WebRtcVideoMediaChannel::ConfigureChannel(int channel_id) {
// Register external transport.
if (engine_->vie()->network()->RegisterSendTransport(
channel_id, *this) != 0) {
LOG_RTCERR1(RegisterSendTransport, channel_id);
return false;
}
// Set MTU.
if (engine_->vie()->network()->SetMTU(channel_id, kVideoMtu) != 0) {
LOG_RTCERR2(SetMTU, channel_id, kVideoMtu);
return false;
}
// Turn on RTCP and loss feedback reporting.
if (engine()->vie()->rtp()->SetRTCPStatus(
channel_id, webrtc::kRtcpCompound_RFC4585) != 0) {
LOG_RTCERR2(SetRTCPStatus, channel_id, webrtc::kRtcpCompound_RFC4585);
return false;
}
// Enable pli as key frame request method.
if (engine_->vie()->rtp()->SetKeyFrameRequestMethod(
channel_id, webrtc::kViEKeyFrameRequestPliRtcp) != 0) {
LOG_RTCERR2(SetKeyFrameRequestMethod,
channel_id, webrtc::kViEKeyFrameRequestPliRtcp);
return false;
}
#ifdef WEBRTC_VIDEO_AVPF_NACK_ONLY
// Turn on NACK-only loss handling.
if (engine_->vie()->rtp()->SetNACKStatus(channel_id, true) != 0) {
LOG_RTCERR1(SetNACKStatus, channel_id);
return false;
}
#endif
return true;
}
bool WebRtcVideoMediaChannel::ConfigureReceiving(int channel_id,
uint32 remote_ssrc) {
// Connect the voice channel, if there is one.
// TODO: The A/V is synched by the receiving channel. So we need to
// know the SSRC of the remote audio channel in order to fetch the correct
// webrtc VoiceEngine channel. For now- only sync the default channel used
// in 1-1 calls.
if (remote_ssrc == 0 && voice_channel_) {
WebRtcVoiceMediaChannel* voice_channel =
static_cast<WebRtcVoiceMediaChannel*>(voice_channel_);
if (engine_->vie()->base()->ConnectAudioChannel(
vie_channel_, voice_channel->voe_channel()) != 0) {
LOG_RTCERR2(ConnectAudioChannel, channel_id,
voice_channel->voe_channel());
LOG(LS_WARNING) << "A/V not synchronized";
// Not a fatal error.
}
}
// Install a render adapter.
talk_base::scoped_ptr<WebRtcVideoChannelInfo> channel_info(
new WebRtcVideoChannelInfo(channel_id));
if (engine_->vie()->render()->AddRenderer(channel_id,
webrtc::kVideoI420, channel_info->render_adapter()) != 0) {
LOG_RTCERR3(AddRenderer, channel_id, webrtc::kVideoI420,
channel_info->render_adapter());
return false;
}
// Turn on TMMBR-based BWE reporting.
// TODO: We should use REMB per default when it is implemented.
if (engine_->vie()->rtp()->SetTMMBRStatus(channel_id, true) != 0) {
LOG_RTCERR1(SetTMMBRStatus, channel_id);
return false;
}
if (remote_ssrc != 0) {
// Use the same SSRC as our default channel
// (so the RTCP reports are correct).
unsigned int send_ssrc = 0;
webrtc::ViERTP_RTCP* rtp = engine()->vie()->rtp();
if (rtp->GetLocalSSRC(vie_channel_, send_ssrc) == -1) {
LOG_RTCERR2(GetSendSSRC, channel_id, send_ssrc);
return false;
}
if (rtp->SetLocalSSRC(channel_id, send_ssrc) == -1) {
LOG_RTCERR2(SetSendSSRC, channel_id, send_ssrc);
return false;
}
} // Else this is the the default channel and we don't change the SSRC.
// Disable color enhancement since it is a bit too aggressive.
if (engine()->vie()->image()->EnableColorEnhancement(channel_id,
false) != 0) {
LOG_RTCERR1(EnableColorEnhancement, channel_id);
return false;
}
if (!SetReceiveCodecs(channel_id)) {
return false;
}
if (render_started_) {
if (engine_->vie()->render()->StartRender(channel_id) != 0) {
LOG_RTCERR1(StartRender, channel_id);
return false;
}
}
// Register decoder observer for incoming framerate and bitrate.
if (engine()->vie()->codec()->RegisterDecoderObserver(
channel_id, *channel_info->decoder_observer()) != 0) {
LOG_RTCERR1(RegisterDecoderObserver, channel_info->decoder_observer());
return false;
}
mux_channels_[remote_ssrc] = channel_info.release();
return true;
}
bool WebRtcVideoMediaChannel::SetNackFec(int red_payload_type,
int fec_payload_type) {
bool enable = (red_payload_type != -1 && fec_payload_type != -1);
if (engine_->vie()->rtp()->SetHybridNACKFECStatus(
vie_channel_, enable, red_payload_type, fec_payload_type) != 0) {
LOG_RTCERR4(SetHybridNACKFECStatus,
vie_channel_, enable, red_payload_type, fec_payload_type);
return false;
}
return true;
}
bool WebRtcVideoMediaChannel::SetSendCodec(const webrtc::VideoCodec& codec,
int min_bitrate,
int start_bitrate,
int max_bitrate) {
// Make a copy of the codec
webrtc::VideoCodec target_codec = codec;
target_codec.startBitrate = start_bitrate;
target_codec.minBitrate = min_bitrate;
target_codec.maxBitrate = max_bitrate;
if (engine()->vie()->codec()->SetSendCodec(vie_channel_, target_codec) != 0) {
LOG_RTCERR2(SetSendCodec, vie_channel_, send_codec_->plName);
return false;
}
// Reset the send_codec_ only if SetSendCodec is success.
send_codec_.reset(new webrtc::VideoCodec(target_codec));
send_min_bitrate_ = min_bitrate;
send_start_bitrate_ = start_bitrate;
send_max_bitrate_ = max_bitrate;
return true;
}
bool WebRtcVideoMediaChannel::SetReceiveCodecs(int channel_id) {
for (std::vector<webrtc::VideoCodec>::iterator it = receive_codecs_.begin();
it != receive_codecs_.end(); ++it) {
if (engine()->vie()->codec()->SetReceiveCodec(channel_id, *it) != 0) {
LOG_RTCERR2(SetReceiveCodec, channel_id, it->plName);
return false;
}
}
// Start receiving packets if at least one receive codec has been set.
if (!receive_codecs_.empty()) {
if (engine()->vie()->base()->StartReceive(channel_id) != 0) {
LOG_RTCERR1(StartReceive, channel_id);
return false;
}
}
return true;
}
// If the new frame size is different from the send codec size we set on vie,
// we need to reset the send codec on vie.
// The new send codec size should not exceed send_codec_ which is controlled
// only by the 'jec' logic.
bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec(int new_width,
int new_height,
bool* reset) {
if (reset) {
*reset = false;
}
if (NULL == send_codec_.get()) {
return false;
}
// Vie send codec size should not exceed send_codec_.
int target_width = new_width;
int target_height = new_height;
if (new_width > send_codec_->width || new_height > send_codec_->height) {
target_width = send_codec_->width;
target_height = send_codec_->height;
}
// Get current vie codec.
webrtc::VideoCodec vie_codec;
if (engine()->vie()->codec()->GetSendCodec(vie_channel_, vie_codec) != 0) {
LOG_RTCERR1(GetSendCodec, vie_channel_);
return false;
}
// Only reset send codec when there is a size change.
if (target_width != vie_codec.width || target_height != vie_codec.height) {
// Set the new codec on vie.
vie_codec.width = target_width;
vie_codec.height = target_height;
if (engine()->vie()->codec()->SetSendCodec(vie_channel_, vie_codec) != 0) {
LOG_RTCERR1(SetSendCodec, vie_channel_);
return false;
}
if (reset) {
*reset = true;
}
LOG(LS_INFO) << "Reset vie send codec to: "
<< vie_codec.width << "x" << vie_codec.height;
}
return true;
}
int WebRtcVideoMediaChannel::SendPacket(int channel, const void* data,
int len) {
if (!network_interface_) {
return -1;
}
talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
return network_interface_->SendPacket(&packet) ? len : -1;
}
int WebRtcVideoMediaChannel::SendRTCPPacket(int channel,
const void* data,
int len) {
if (!network_interface_) {
return -1;
}
talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
return network_interface_->SendRtcp(&packet) ? len : -1;
}
} // namespace cricket
#endif // HAVE_WEBRTC_VIDEO