summaryrefslogtreecommitdiffstats
path: root/third_party/libjingle/overrides/talk/session/phone/webrtcvideoengine.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libjingle/overrides/talk/session/phone/webrtcvideoengine.cc')
-rw-r--r--third_party/libjingle/overrides/talk/session/phone/webrtcvideoengine.cc1619
1 files changed, 1619 insertions, 0 deletions
diff --git a/third_party/libjingle/overrides/talk/session/phone/webrtcvideoengine.cc b/third_party/libjingle/overrides/talk/session/phone/webrtcvideoengine.cc
new file mode 100644
index 0000000..829fac3
--- /dev/null
+++ b/third_party/libjingle/overrides/talk/session/phone/webrtcvideoengine.cc
@@ -0,0 +1,1619 @@
+/*
+ * 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/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;
+ }
+ 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);
+
+ 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();
+ }
+
+ 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:
+ 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:
+ 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 LocalStreamInfo {
+ 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_;
+};
+
+const WebRtcVideoEngine::VideoCodecPref
+ WebRtcVideoEngine::kVideoCodecPrefs[] = {
+ {kVp8PayloadName, 100, 0},
+#ifndef WEBRTC_VIDEO_AVPF_NACK_ONLY
+ {kRedPayloadName, 101, 1},
+ {kFecPayloadName, 102, 2},
+#endif
+};
+
+// 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, 30, FOURCC_ANY},
+ {1280, 720, 30, FOURCC_ANY},
+ {960, 600, 30, FOURCC_ANY},
+ {960, 540, 30, FOURCC_ANY},
+ {640, 400, 30, FOURCC_ANY},
+ {640, 360, 30, FOURCC_ANY},
+ {640, 480, 30, FOURCC_ANY},
+ {480, 300, 30, FOURCC_ANY},
+ {480, 270, 30, FOURCC_ANY},
+ {480, 360, 30, FOURCC_ANY},
+ {320, 200, 30, FOURCC_ANY},
+ {320, 180, 30, FOURCC_ANY},
+ {320, 240, 30, FOURCC_ANY},
+ {240, 150, 30, FOURCC_ANY},
+ {240, 135, 30, FOURCC_ANY},
+ {240, 180, 30, FOURCC_ANY},
+ {160, 100, 30, FOURCC_ANY},
+ {160, 90, 30, FOURCC_ANY},
+ {160, 120, 30, FOURCC_ANY},
+};
+
+const VideoFormatPod WebRtcVideoEngine::kDefaultVideoFormat =
+ {640, 400, 30, 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,
+ kDefaultVideoFormat.framerate,
+ 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) {
+ if (!device) {
+ ClearCapturer();
+ 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;
+ }
+ const bool owns_capturer = true;
+ 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) {
+ if (!vcm) {
+ if ((video_capturer_ != NULL) && video_capturer_->IsRunning()) {
+ LOG(LS_WARNING) << "Failed to set camera to NULL when is running.";
+ return false;
+ } else {
+ ClearCapturer();
+ 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;
+ }
+ const bool owns_capturer = true;
+ if (!SetCapturer(capturer, owns_capturer)) {
+ return false;
+ }
+ LOG(LS_INFO) << "Camera created with VCM";
+ CaptureResult ret = SetCapture(true);
+ if (ret != cricket::CR_SUCCESS && ret != cricket::CR_PENDING) {
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcVideoEngine::SetVideoCapturer(VideoCapturer* capturer,
+ uint32 /*ssrc*/) {
+ const bool capture = (capturer != NULL);
+ const bool owns_capturer = false;
+ CaptureResult res = CR_FAILURE;
+ if (capture) {
+ // Register the capturer before starting to capture.
+ if (!SetCapturer(capturer, owns_capturer)) {
+ return false;
+ }
+ const bool kEnableCapture = true;
+ res = SetCapture(kEnableCapture);
+ } else {
+ // Stop capturing before unregistering the capturer.
+ const bool kDisableCapture = false;
+ res = SetCapture(kDisableCapture);
+ if (!SetCapturer(capturer, owns_capturer)) {
+ return false;
+ }
+ }
+ return (res == CR_SUCCESS) || (res == CR_PENDING);
+}
+
+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) {
+ 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 LocalStreamInfo()) {
+ engine->RegisterChannel(this);
+}
+
+bool WebRtcVideoMediaChannel::Init() {
+ if (engine_->vie()->base()->CreateChannel(vie_channel_) != 0) {
+ LOG_RTCERR1(CreateChannel, vie_channel_);
+ return false;
+ }
+
+ LOG(LS_INFO) << "WebRtcVideoMediaChannel::Init "
+ << "vie_channel " << vie_channel_ << " created";
+
+ // Connect the voice channel, if there is one.
+ if (voice_channel_) {
+ WebRtcVoiceMediaChannel* channel =
+ static_cast<WebRtcVoiceMediaChannel*>(voice_channel_);
+ if (engine_->vie()->base()->ConnectAudioChannel(
+ vie_channel_, channel->voe_channel()) != 0) {
+ LOG_RTCERR2(ConnectAudioChannel, vie_channel_, channel->voe_channel());
+ LOG(LS_WARNING) << "A/V not synchronized";
+ // Not a fatal error.
+ }
+ }
+
+ // Register external transport.
+ if (engine_->vie()->network()->RegisterSendTransport(
+ vie_channel_, *this) != 0) {
+ LOG_RTCERR1(RegisterSendTransport, vie_channel_);
+ return false;
+ }
+
+ // Set MTU.
+ if (engine_->vie()->network()->SetMTU(vie_channel_, kVideoMtu) != 0) {
+ LOG_RTCERR2(SetMTU, vie_channel_, kVideoMtu);
+ return false;
+ }
+
+ // 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;
+ }
+
+ // Install render adapter.
+ remote_renderer_.reset(new WebRtcRenderAdapter(NULL));
+ if (engine_->vie()->render()->AddRenderer(vie_channel_,
+ webrtc::kVideoI420, remote_renderer_.get()) != 0) {
+ LOG_RTCERR3(AddRenderer, vie_channel_, webrtc::kVideoI420,
+ remote_renderer_.get());
+ remote_renderer_.reset();
+ return false;
+ }
+
+ // Register decoder observer for incoming framerate and bitrate.
+ decoder_observer_.reset(new WebRtcDecoderObserver(vie_channel_));
+ if (engine()->vie()->codec()->RegisterDecoderObserver(
+ vie_channel_, *decoder_observer_) != 0) {
+ LOG_RTCERR1(RegisterDecoderObserver, decoder_observer_.get());
+ 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;
+ }
+
+ // Turn on RTCP and loss feedback reporting.
+ if (!EnableRtcp() ||
+ !EnablePli()) {
+ return false;
+ }
+
+#ifdef WEBRTC_VIDEO_AVPF_NACK_ONLY
+ // Turn on NACK-only loss handling.
+ if (!EnableNack())
+ return false;
+#endif
+
+ // Turn on TMMBR-based BWE reporting.
+ if (!EnableTmmbr()) {
+ 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);
+ if (engine()->vie()->codec()->DeregisterDecoderObserver(
+ vie_channel_) != 0) {
+ LOG_RTCERR1(DeregisterDecoderObserver, vie_channel_);
+ }
+ if (remote_renderer_.get() &&
+ engine()->vie()->render()->RemoveRenderer(vie_channel_) != 0) {
+ LOG_RTCERR1(RemoveRenderer, vie_channel_);
+ }
+
+ // 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_);
+ }
+ }
+
+ // Deregister external transport.
+ if (engine()->vie()->network()->DeregisterSendTransport(
+ vie_channel_) != 0) {
+ LOG_RTCERR1(DeregisterSendTransport, vie_channel_);
+ }
+
+ // Delete the VideoEngine channel.
+ if (engine()->vie()->base()->DeleteChannel(vie_channel_) != 0) {
+ LOG_RTCERR1(DeleteChannel, vie_channel_);
+ }
+ }
+
+ // Unregister the channel from the engine.
+ engine()->UnregisterChannel(this);
+}
+
+bool WebRtcVideoMediaChannel::SetRecvCodecs(
+ const std::vector<VideoCodec>& codecs) {
+ bool ret = true;
+ 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)) {
+ if (engine()->vie()->codec()->SetReceiveCodec(
+ vie_channel_, wcodec) != 0) {
+ LOG_RTCERR2(SetReceiveCodec, vie_channel_, wcodec.plName);
+ ret = false;
+ }
+ }
+ } else {
+ LOG(LS_INFO) << "Unknown codec " << iter->name;
+ ret = false;
+ }
+ }
+
+ // make channel ready to receive packets
+ if (ret) {
+ if (engine()->vie()->base()->StartReceive(vie_channel_) != 0) {
+ LOG_RTCERR1(StartReceive, vie_channel_);
+ ret = false;
+ }
+ }
+ return ret;
+}
+
+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;
+ if (render) {
+ if (engine()->vie()->render()->StartRender(vie_channel_) != 0) {
+ LOG_RTCERR1(StartRender, vie_channel_);
+ ret = false;
+ }
+ } else {
+ if (engine()->vie()->render()->StopRender(vie_channel_) != 0) {
+ LOG_RTCERR1(StopRender, vie_channel_);
+ 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;
+}
+
+bool WebRtcVideoMediaChannel::AddStream(uint32 ssrc, uint32 voice_ssrc) {
+ return false;
+}
+
+bool WebRtcVideoMediaChannel::RemoveStream(uint32 ssrc) {
+ return false;
+}
+
+bool WebRtcVideoMediaChannel::SetRenderer(
+ uint32 ssrc, VideoRenderer* renderer) {
+ if (ssrc != 0)
+ return false;
+
+ remote_renderer_->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()->GetReceivedRTCPStatistics(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 receiver statistics and build VideoReceiverInfo, if we have data.
+ if (engine_->vie()->rtp()->GetRemoteSSRC(vie_channel_, ssrc) == 0) {
+ 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 = decoder_observer_->firs_requested();
+ rinfo.nacks_sent = -1;
+ rinfo.frame_width = remote_renderer_->width();
+ rinfo.frame_height = remote_renderer_->height();
+ rinfo.framerate_rcvd = decoder_observer_->framerate();
+ int fps = remote_renderer_->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()->GetSentRTCPStatistics(vie_channel_,
+ 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) {
+ engine()->vie()->network()->ReceivedRTPPacket(vie_channel_,
+ packet->data(),
+ packet->length());
+}
+
+void WebRtcVideoMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
+ 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) {
+ 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);
+ }
+}
+
+// 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());
+
+ // If the captured video format is smaller than what we asked for, reset send
+ // codec on video engine.
+ if (send_codec_.get() != NULL &&
+ frame->GetWidth() < send_codec_->width &&
+ frame->GetHeight() < send_codec_->height) {
+ LOG(LS_INFO) << "Captured video frame size changed to: "
+ << frame->GetWidth() << "x" << frame->GetHeight();
+ webrtc::VideoCodec new_codec = *send_codec_;
+ new_codec.width = frame->GetWidth();
+ new_codec.height = frame->GetHeight();
+ if (!SetSendCodec(
+ new_codec, send_min_bitrate_, send_start_bitrate_, send_max_bitrate_)) {
+ LOG(LS_WARNING) << "Failed to switch to new frame size: "
+ << frame->GetWidth() << "x" << frame->GetHeight();
+ }
+ }
+
+ // 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::EnableRtcp() {
+ if (engine()->vie()->rtp()->SetRTCPStatus(
+ vie_channel_, webrtc::kRtcpCompound_RFC4585) != 0) {
+ LOG_RTCERR2(SetRTCPStatus, vie_channel_, webrtc::kRtcpCompound_RFC4585);
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::EnablePli() {
+ if (engine_->vie()->rtp()->SetKeyFrameRequestMethod(
+ vie_channel_, webrtc::kViEKeyFrameRequestPliRtcp) != 0) {
+ LOG_RTCERR2(SetRTCPStatus,
+ vie_channel_, webrtc::kViEKeyFrameRequestPliRtcp);
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::EnableTmmbr() {
+ if (engine_->vie()->rtp()->SetTMMBRStatus(vie_channel_, true) != 0) {
+ LOG_RTCERR1(SetTMMBRStatus, vie_channel_);
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::EnableNack() {
+ if (engine_->vie()->rtp()->SetNACKStatus(vie_channel_, true) != 0) {
+ LOG_RTCERR1(SetNACKStatus, vie_channel_);
+ return false;
+ }
+ 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;
+}
+
+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