diff options
author | ronghuawu@chromium.org <ronghuawu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-14 04:30:53 +0000 |
---|---|---|
committer | ronghuawu@chromium.org <ronghuawu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-14 04:30:53 +0000 |
commit | e7b20aed3e550835125b83d5c4c2b1d2514045a6 (patch) | |
tree | 1c7fa470ed52277e012ac3fe01cd31ce44bbf23e /third_party | |
parent | ded7d0df2fc691e41b2d36a626f02de1ca55b89f (diff) | |
download | chromium_src-e7b20aed3e550835125b83d5c4c2b1d2514045a6.zip chromium_src-e7b20aed3e550835125b83d5c4c2b1d2514045a6.tar.gz chromium_src-e7b20aed3e550835125b83d5c4c2b1d2514045a6.tar.bz2 |
A fix for the PeerConnection one way no video issue.
BUG=N/A
TEST=peerconnection test
Review URL: http://codereview.chromium.org/9216002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@117780 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'third_party')
-rw-r--r-- | third_party/libjingle/libjingle.gyp | 6 | ||||
-rw-r--r-- | third_party/libjingle/overrides/talk/session/phone/webrtcvideoengine.cc | 1619 |
2 files changed, 1623 insertions, 2 deletions
diff --git a/third_party/libjingle/libjingle.gyp b/third_party/libjingle/libjingle.gyp index dc4d652..99ef685 100644 --- a/third_party/libjingle/libjingle.gyp +++ b/third_party/libjingle/libjingle.gyp @@ -1,4 +1,4 @@ -# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Copyright (c) 2012 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -533,7 +533,9 @@ 'source/talk/session/phone/webrtcpassthroughrender.cc', 'source/talk/session/phone/webrtcvideocapturer.cc', 'source/talk/session/phone/webrtcvideocapturer.h', - 'source/talk/session/phone/webrtcvideoengine.cc', + # TODO(ronghuawu): Fix this in libjingle and remove the override. + # See http://crbug.com/110255 + 'overrides/talk/session/phone/webrtcvideoengine.cc', 'source/talk/session/phone/webrtcvideoengine.h', 'source/talk/session/phone/webrtcvideoframe.cc', 'source/talk/session/phone/webrtcvideoframe.h', 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 |