
Original cl description: This removes the none const pointer entry and SwapFrame. Since frames delivered using VideoSendStream no longer use the external capture module, VideoSendStream will not get an incoming framerate callback. VideoSendStream now uses a rtc::RateTracker. Also, the video engine must ensure that time stamps are always increasing. With this, time stamps (ntp, render_time and rtp timestamps ) are checked and set in ViECapturer::OnIncomingCapturedFrame This cl was previously reverted in https://webrtc-codereview.appspot.com/46549004/. Patchset 1 contains the original patch after rebase. Patshet 2 fix webrtc_perf_tests reported in chromium:465306 Note that chromium:465287 is being fixed in https://webrtc-codereview.appspot.com/43829004/ BUG=1128 R=magjed@webrtc.org, mflodman@webrtc.org Review URL: https://webrtc-codereview.appspot.com/47629004 Cr-Commit-Position: refs/heads/master@{#8776} git-svn-id: http://webrtc.googlecode.com/svn/trunk@8776 4adac7df-926f-26a2-2b94-8c16560cd09d
4260 lines
145 KiB
C++
4260 lines
145 KiB
C++
/*
|
|
* libjingle
|
|
* Copyright 2004 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_WEBRTC_VIDEO
|
|
#include "talk/media/webrtc/webrtcvideoengine.h"
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <math.h>
|
|
#include <algorithm>
|
|
#include <set>
|
|
|
|
#include "talk/media/base/constants.h"
|
|
#include "talk/media/base/rtputils.h"
|
|
#include "talk/media/base/streamparams.h"
|
|
#include "talk/media/base/videoadapter.h"
|
|
#include "talk/media/base/videocapturer.h"
|
|
#include "talk/media/base/videorenderer.h"
|
|
#include "talk/media/devices/filevideocapturer.h"
|
|
#include "talk/media/webrtc/constants.h"
|
|
#include "talk/media/webrtc/simulcast.h"
|
|
#include "talk/media/webrtc/webrtcpassthroughrender.h"
|
|
#include "talk/media/webrtc/webrtcvideocapturer.h"
|
|
#include "talk/media/webrtc/webrtcvideodecoderfactory.h"
|
|
#include "talk/media/webrtc/webrtcvideoencoderfactory.h"
|
|
#include "talk/media/webrtc/webrtcvideoframe.h"
|
|
#include "talk/media/webrtc/webrtcvie.h"
|
|
#include "talk/media/webrtc/webrtcvoe.h"
|
|
#include "talk/media/webrtc/webrtcvoiceengine.h"
|
|
#include "webrtc/base/basictypes.h"
|
|
#include "webrtc/base/buffer.h"
|
|
#include "webrtc/base/byteorder.h"
|
|
#include "webrtc/base/checks.h"
|
|
#include "webrtc/base/common.h"
|
|
#include "webrtc/base/cpumonitor.h"
|
|
#include "webrtc/base/logging.h"
|
|
#include "webrtc/base/stringutils.h"
|
|
#include "webrtc/base/thread.h"
|
|
#include "webrtc/base/timeutils.h"
|
|
#include "webrtc/experiments.h"
|
|
#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
|
|
#include "webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.h"
|
|
#include "webrtc/modules/video_coding/codecs/vp8/vp8_factory.h"
|
|
#include "webrtc/system_wrappers/interface/field_trial.h"
|
|
|
|
namespace {
|
|
|
|
cricket::VideoFormat CreateVideoFormat(int width, int height, int framerate) {
|
|
return cricket::VideoFormat(
|
|
width,
|
|
height,
|
|
cricket::VideoFormat::FpsToInterval(framerate),
|
|
cricket::FOURCC_ANY);
|
|
}
|
|
|
|
cricket::VideoFormat VideoFormatFromCodec(const cricket::VideoCodec& codec) {
|
|
return CreateVideoFormat(codec.width, codec.height, codec.framerate);
|
|
}
|
|
|
|
cricket::VideoFormat VideoFormatFromVieCodec(const webrtc::VideoCodec& codec) {
|
|
return CreateVideoFormat(codec.width, codec.height, codec.maxFramerate);
|
|
}
|
|
|
|
template <class T>
|
|
void Clamp(cricket::Settable<T>* box, T min, T max) {
|
|
T val;
|
|
if (!box->Get(&val)) {
|
|
return;
|
|
}
|
|
if (val < min) {
|
|
box->Set(min);
|
|
return;
|
|
}
|
|
if (val > max) {
|
|
box->Set(max);
|
|
return;
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
bool Changed(cricket::Settable<T> proposed,
|
|
cricket::Settable<T> original) {
|
|
return proposed.IsSet() && proposed != original;
|
|
}
|
|
|
|
template <class T>
|
|
bool Changed(cricket::Settable<T> proposed,
|
|
cricket::Settable<T> original,
|
|
T* value) {
|
|
return proposed.Get(value) && proposed != original;
|
|
}
|
|
|
|
// Wrap cricket::WebRtcVideoEncoderFactory as a webrtc::VideoEncoderFactory.
|
|
class EncoderFactoryAdapter : public webrtc::VideoEncoderFactory {
|
|
public:
|
|
// EncoderFactoryAdapter doesn't take ownership of |factory|, which is owned
|
|
// by e.g. PeerConnectionFactory.
|
|
explicit EncoderFactoryAdapter(cricket::WebRtcVideoEncoderFactory* factory)
|
|
: factory_(factory) {}
|
|
virtual ~EncoderFactoryAdapter() {}
|
|
|
|
// Implement webrtc::VideoEncoderFactory.
|
|
webrtc::VideoEncoder* Create() override {
|
|
return factory_->CreateVideoEncoder(webrtc::kVideoCodecVP8);
|
|
}
|
|
|
|
void Destroy(webrtc::VideoEncoder* encoder) override {
|
|
return factory_->DestroyVideoEncoder(encoder);
|
|
}
|
|
|
|
private:
|
|
cricket::WebRtcVideoEncoderFactory* factory_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
namespace cricket {
|
|
|
|
// Constants defined in talk/media/webrtc/constants.h
|
|
// TODO(pbos): Move these to a separate constants.cc file.
|
|
const int kVideoMtu = 1200;
|
|
const int kVideoRtpBufferSize = 65536;
|
|
|
|
const char kVp8CodecName[] = "VP8";
|
|
const char kVp9CodecName[] = "VP9";
|
|
|
|
// TODO(ronghuawu): Change to 640x360.
|
|
const int kDefaultVideoMaxWidth = 640;
|
|
const int kDefaultVideoMaxHeight = 400;
|
|
const int kDefaultVideoMaxFramerate = 30;
|
|
const int kMinVideoBitrate = 30;
|
|
const int kStartVideoBitrate = 300;
|
|
const int kMaxVideoBitrate = 2000;
|
|
|
|
const int kCpuMonitorPeriodMs = 2000; // 2 seconds.
|
|
|
|
// TODO(pthatcher): Figure out what the proper value here is, or if we
|
|
// can just remove this altogether.
|
|
static const int kDefaultRenderDelayMs = 100;
|
|
|
|
static const int kDefaultLogSeverity = rtc::LS_WARNING;
|
|
|
|
static const int kDefaultNumberOfTemporalLayers = 1; // 1:1
|
|
|
|
static const int kChannelIdUnset = -1;
|
|
static const uint32 kDefaultChannelSsrcKey = 0;
|
|
static const uint32 kSsrcUnset = 0;
|
|
|
|
static bool BitrateIsSet(int value) {
|
|
return value > kAutoBandwidth;
|
|
}
|
|
|
|
static int GetBitrate(int value, int deflt) {
|
|
return BitrateIsSet(value) ? value : deflt;
|
|
}
|
|
|
|
// Static allocation of payload type values for external video codec.
|
|
static int GetExternalVideoPayloadType(size_t index) {
|
|
static const int kExternalVideoPayloadTypeBase = 120;
|
|
index += kExternalVideoPayloadTypeBase;
|
|
ASSERT(index < 128);
|
|
return static_cast<int>(index);
|
|
}
|
|
|
|
static void LogMultiline(rtc::LoggingSeverity sev, char* text) {
|
|
const char* delim = "\r\n";
|
|
// TODO(fbarchard): Fix strtok lint warning.
|
|
for (char* tok = strtok(text, delim); tok; tok = strtok(NULL, delim)) {
|
|
LOG_V(sev) << tok;
|
|
}
|
|
}
|
|
|
|
// Severity is an integer because it comes is assumed to be from command line.
|
|
static int SeverityToFilter(int severity) {
|
|
int filter = webrtc::kTraceNone;
|
|
switch (severity) {
|
|
case rtc::LS_VERBOSE:
|
|
filter |= webrtc::kTraceAll;
|
|
case rtc::LS_INFO:
|
|
filter |= (webrtc::kTraceStateInfo | webrtc::kTraceInfo);
|
|
case rtc::LS_WARNING:
|
|
filter |= (webrtc::kTraceTerseInfo | webrtc::kTraceWarning);
|
|
case rtc::LS_ERROR:
|
|
filter |= (webrtc::kTraceError | webrtc::kTraceCritical);
|
|
}
|
|
return filter;
|
|
}
|
|
|
|
static const bool kNotSending = false;
|
|
|
|
// Default video dscp value.
|
|
// See http://tools.ietf.org/html/rfc2474 for details
|
|
// See also http://tools.ietf.org/html/draft-jennings-rtcweb-qos-00
|
|
static const rtc::DiffServCodePoint kVideoDscpValue =
|
|
rtc::DSCP_AF41;
|
|
|
|
bool IsNackEnabled(const VideoCodec& codec) {
|
|
return codec.HasFeedbackParam(
|
|
FeedbackParam(kRtcpFbParamNack, kParamValueEmpty));
|
|
}
|
|
|
|
bool IsRembEnabled(const VideoCodec& codec) {
|
|
return codec.HasFeedbackParam(
|
|
FeedbackParam(kRtcpFbParamRemb, kParamValueEmpty));
|
|
}
|
|
|
|
void AddDefaultFeedbackParams(VideoCodec* codec) {
|
|
codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamCcm, kRtcpFbCcmParamFir));
|
|
codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamNack, kParamValueEmpty));
|
|
codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamNack, kRtcpFbNackParamPli));
|
|
codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamRemb, kParamValueEmpty));
|
|
}
|
|
|
|
bool CodecNameMatches(const std::string& name1, const std::string& name2) {
|
|
return _stricmp(name1.c_str(), name2.c_str()) == 0;
|
|
}
|
|
|
|
static VideoCodec MakeVideoCodecWithDefaultFeedbackParams(int payload_type,
|
|
const char* name) {
|
|
VideoCodec codec(payload_type, name, kDefaultVideoMaxWidth,
|
|
kDefaultVideoMaxHeight, kDefaultVideoMaxFramerate, 0);
|
|
AddDefaultFeedbackParams(&codec);
|
|
return codec;
|
|
}
|
|
|
|
static VideoCodec MakeVideoCodec(int payload_type, const char* name) {
|
|
return VideoCodec(payload_type, name, 0, 0, 0, 0);
|
|
}
|
|
|
|
static VideoCodec MakeRtxCodec(int payload_type, int associated_payload_type) {
|
|
return VideoCodec::CreateRtxCodec(payload_type, associated_payload_type);
|
|
}
|
|
|
|
bool CodecIsInternallySupported(const std::string& codec_name) {
|
|
if (CodecNameMatches(codec_name, kVp8CodecName)) {
|
|
return true;
|
|
}
|
|
if (CodecNameMatches(codec_name, kVp9CodecName)) {
|
|
const std::string group_name =
|
|
webrtc::field_trial::FindFullName("WebRTC-SupportVP9");
|
|
return group_name == "Enabled" || group_name == "EnabledByFlag";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<VideoCodec> DefaultVideoCodecList() {
|
|
std::vector<VideoCodec> codecs;
|
|
if (CodecIsInternallySupported(kVp9CodecName)) {
|
|
codecs.push_back(
|
|
MakeVideoCodecWithDefaultFeedbackParams(101, kVp9CodecName));
|
|
// TODO(andresp): Add rtx codec for vp9 and verify it works.
|
|
}
|
|
codecs.push_back(MakeVideoCodecWithDefaultFeedbackParams(100, kVp8CodecName));
|
|
codecs.push_back(MakeRtxCodec(96, 100));
|
|
codecs.push_back(MakeVideoCodec(116, kRedCodecName));
|
|
codecs.push_back(MakeVideoCodec(117, kUlpfecCodecName));
|
|
return codecs;
|
|
}
|
|
|
|
WebRtcSimulcastEncoderFactory::WebRtcSimulcastEncoderFactory(
|
|
cricket::WebRtcVideoEncoderFactory* factory)
|
|
: factory_(factory) {
|
|
}
|
|
|
|
WebRtcSimulcastEncoderFactory::~WebRtcSimulcastEncoderFactory() {
|
|
}
|
|
|
|
bool WebRtcSimulcastEncoderFactory::UseSimulcastEncoderFactory(
|
|
const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs) {
|
|
// If any codec is VP8, use the simulcast factory. If asked to create a
|
|
// non-VP8 codec, we'll just return a contained factory encoder directly.
|
|
for (const auto& codec: codecs) {
|
|
if (codec.type == webrtc::kVideoCodecVP8) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
webrtc::VideoEncoder* WebRtcSimulcastEncoderFactory::CreateVideoEncoder(
|
|
webrtc::VideoCodecType type) {
|
|
ASSERT(factory_ != NULL);
|
|
// If it's a codec type we can simulcast, create a wrapped encoder.
|
|
if (type == webrtc::kVideoCodecVP8) {
|
|
return new webrtc::SimulcastEncoderAdapter(
|
|
new EncoderFactoryAdapter(factory_));
|
|
}
|
|
webrtc::VideoEncoder* encoder = factory_->CreateVideoEncoder(type);
|
|
if (encoder) {
|
|
non_simulcast_encoders_.push_back(encoder);
|
|
}
|
|
return encoder;
|
|
}
|
|
|
|
const std::vector<WebRtcVideoEncoderFactory::VideoCodec>&
|
|
WebRtcSimulcastEncoderFactory::codecs() const {
|
|
return factory_->codecs();
|
|
}
|
|
|
|
bool WebRtcSimulcastEncoderFactory::EncoderTypeHasInternalSource(
|
|
webrtc::VideoCodecType type) const {
|
|
return factory_->EncoderTypeHasInternalSource(type);
|
|
}
|
|
|
|
void WebRtcSimulcastEncoderFactory::DestroyVideoEncoder(
|
|
webrtc::VideoEncoder* encoder) {
|
|
// Check first to see if the encoder wasn't wrapped in a
|
|
// SimulcastEncoderAdapter. In that case, ask the factory to destroy it.
|
|
if (std::remove(non_simulcast_encoders_.begin(),
|
|
non_simulcast_encoders_.end(), encoder) !=
|
|
non_simulcast_encoders_.end()) {
|
|
factory_->DestroyVideoEncoder(encoder);
|
|
return;
|
|
}
|
|
|
|
// Otherwise, SimulcastEncoderAdapter can be deleted directly, and will call
|
|
// DestroyVideoEncoder on the factory for individual encoder instances.
|
|
delete encoder;
|
|
}
|
|
|
|
struct FlushBlackFrameData : public rtc::MessageData {
|
|
FlushBlackFrameData(uint32 s, int64 t, int i)
|
|
: ssrc(s), timestamp(t), interval(i) {
|
|
}
|
|
uint32 ssrc;
|
|
int64 timestamp;
|
|
int interval;
|
|
};
|
|
|
|
class WebRtcRenderAdapter : public webrtc::ExternalRenderer {
|
|
public:
|
|
WebRtcRenderAdapter(VideoRenderer* renderer, int channel_id)
|
|
: renderer_(renderer),
|
|
channel_id_(channel_id),
|
|
width_(0),
|
|
height_(0),
|
|
capture_start_rtp_time_stamp_(-1),
|
|
capture_start_ntp_time_ms_(0) {
|
|
}
|
|
|
|
virtual ~WebRtcRenderAdapter() {
|
|
}
|
|
|
|
void SetRenderer(VideoRenderer* renderer) {
|
|
rtc::CritScope cs(&crit_);
|
|
renderer_ = renderer;
|
|
// FrameSizeChange may have already been called when renderer was not set.
|
|
// If so we should call SetSize here.
|
|
// TODO(ronghuawu): Add unit test for this case. Didn't do it now
|
|
// because the WebRtcRenderAdapter is currently hiding in cc file. No
|
|
// good way to get access to it from the unit test.
|
|
if (width_ > 0 && height_ > 0 && renderer_) {
|
|
if (!renderer_->SetSize(width_, height_, 0)) {
|
|
LOG(LS_ERROR)
|
|
<< "WebRtcRenderAdapter (channel " << channel_id_
|
|
<< ") SetRenderer failed to SetSize to: "
|
|
<< width_ << "x" << height_;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implementation of webrtc::ExternalRenderer.
|
|
virtual int FrameSizeChange(unsigned int width, unsigned int height,
|
|
unsigned int /*number_of_streams*/) {
|
|
rtc::CritScope cs(&crit_);
|
|
width_ = width;
|
|
height_ = height;
|
|
LOG(LS_INFO) << "WebRtcRenderAdapter (channel " << channel_id_
|
|
<< ") frame size changed to: "
|
|
<< width << "x" << height;
|
|
if (!renderer_) {
|
|
LOG(LS_VERBOSE) << "WebRtcRenderAdapter (channel " << channel_id_
|
|
<< ") the renderer has not been set. "
|
|
<< "SetSize will be called later in SetRenderer.";
|
|
return 0;
|
|
}
|
|
return renderer_->SetSize(width_, height_, 0) ? 0 : -1;
|
|
}
|
|
|
|
virtual int DeliverFrame(unsigned char* buffer,
|
|
size_t buffer_size,
|
|
uint32_t rtp_time_stamp,
|
|
int64_t ntp_time_ms,
|
|
int64_t render_time,
|
|
void* handle) {
|
|
CHECK(false) << "All frames should be delivered as I420 frames through "
|
|
"DeliverI420Frame.";
|
|
return 0;
|
|
}
|
|
|
|
virtual int DeliverI420Frame(const webrtc::I420VideoFrame& webrtc_frame) {
|
|
rtc::CritScope cs(&crit_);
|
|
const int64_t elapsed_time_ms = ElapsedTimeMs(webrtc_frame.timestamp());
|
|
UpdateFrameStats(elapsed_time_ms, webrtc_frame.ntp_time_ms());
|
|
if (!renderer_) {
|
|
return 0;
|
|
}
|
|
|
|
WebRtcVideoFrame cricket_frame(
|
|
webrtc_frame.video_frame_buffer(),
|
|
elapsed_time_ms * rtc::kNumNanosecsPerMillisec,
|
|
webrtc_frame.render_time_ms() * rtc::kNumNanosecsPerMillisec);
|
|
return renderer_->RenderFrame(&cricket_frame) ? 0 : -1;
|
|
}
|
|
|
|
virtual bool IsTextureSupported() { return true; }
|
|
|
|
unsigned int width() {
|
|
rtc::CritScope cs(&crit_);
|
|
return width_;
|
|
}
|
|
|
|
unsigned int height() {
|
|
rtc::CritScope cs(&crit_);
|
|
return height_;
|
|
}
|
|
|
|
int framerate() {
|
|
rtc::CritScope cs(&crit_);
|
|
return static_cast<int>(frame_rate_tracker_.units_second());
|
|
}
|
|
|
|
VideoRenderer* renderer() {
|
|
rtc::CritScope cs(&crit_);
|
|
return renderer_;
|
|
}
|
|
|
|
int64 capture_start_ntp_time_ms() {
|
|
rtc::CritScope cs(&crit_);
|
|
return capture_start_ntp_time_ms_;
|
|
}
|
|
|
|
private:
|
|
int64_t ElapsedTimeMs(uint32_t rtp_time_stamp) {
|
|
if (capture_start_rtp_time_stamp_ < 0) {
|
|
capture_start_rtp_time_stamp_ = rtp_time_stamp;
|
|
}
|
|
const int kVideoCodecClockratekHz = cricket::kVideoCodecClockrate / 1000;
|
|
return (rtp_ts_wraparound_handler_.Unwrap(rtp_time_stamp) -
|
|
capture_start_rtp_time_stamp_) / kVideoCodecClockratekHz;
|
|
}
|
|
|
|
void UpdateFrameStats(int64_t elapsed_time_ms, int64_t ntp_time_ms) {
|
|
if (ntp_time_ms > 0) {
|
|
capture_start_ntp_time_ms_ = ntp_time_ms - elapsed_time_ms;
|
|
}
|
|
frame_rate_tracker_.Update(1);
|
|
}
|
|
|
|
rtc::CriticalSection crit_;
|
|
VideoRenderer* renderer_;
|
|
int channel_id_;
|
|
unsigned int width_;
|
|
unsigned int height_;
|
|
rtc::RateTracker frame_rate_tracker_;
|
|
rtc::TimestampWrapAroundHandler rtp_ts_wraparound_handler_;
|
|
int64 capture_start_rtp_time_stamp_;
|
|
int64 capture_start_ntp_time_ms_;
|
|
};
|
|
|
|
class WebRtcDecoderObserver : public webrtc::ViEDecoderObserver {
|
|
public:
|
|
explicit WebRtcDecoderObserver(int video_channel_id)
|
|
: video_channel_id_(video_channel_id),
|
|
framerate_(0),
|
|
bitrate_(0),
|
|
decode_ms_(0),
|
|
max_decode_ms_(0),
|
|
current_delay_ms_(0),
|
|
target_delay_ms_(0),
|
|
jitter_buffer_ms_(0),
|
|
min_playout_delay_ms_(0),
|
|
render_delay_ms_(0) {
|
|
}
|
|
|
|
// virtual functions from VieDecoderObserver.
|
|
virtual void IncomingCodecChanged(const int video_channel_id,
|
|
const webrtc::VideoCodec& videoCodec) {}
|
|
virtual void IncomingRate(const int video_channel_id,
|
|
const unsigned int framerate,
|
|
const unsigned int bitrate) {
|
|
rtc::CritScope cs(&crit_);
|
|
ASSERT(video_channel_id_ == video_channel_id);
|
|
framerate_ = framerate;
|
|
bitrate_ = bitrate;
|
|
}
|
|
|
|
virtual void DecoderTiming(int decode_ms,
|
|
int max_decode_ms,
|
|
int current_delay_ms,
|
|
int target_delay_ms,
|
|
int jitter_buffer_ms,
|
|
int min_playout_delay_ms,
|
|
int render_delay_ms) {
|
|
rtc::CritScope cs(&crit_);
|
|
decode_ms_ = decode_ms;
|
|
max_decode_ms_ = max_decode_ms;
|
|
current_delay_ms_ = current_delay_ms;
|
|
target_delay_ms_ = target_delay_ms;
|
|
jitter_buffer_ms_ = jitter_buffer_ms;
|
|
min_playout_delay_ms_ = min_playout_delay_ms;
|
|
render_delay_ms_ = render_delay_ms;
|
|
}
|
|
|
|
virtual void RequestNewKeyFrame(const int video_channel_id) {}
|
|
|
|
// Populate |rinfo| based on previously-set data in |*this|.
|
|
void ExportTo(VideoReceiverInfo* rinfo) {
|
|
rtc::CritScope cs(&crit_);
|
|
rinfo->framerate_rcvd = framerate_;
|
|
rinfo->decode_ms = decode_ms_;
|
|
rinfo->max_decode_ms = max_decode_ms_;
|
|
rinfo->current_delay_ms = current_delay_ms_;
|
|
rinfo->target_delay_ms = target_delay_ms_;
|
|
rinfo->jitter_buffer_ms = jitter_buffer_ms_;
|
|
rinfo->min_playout_delay_ms = min_playout_delay_ms_;
|
|
rinfo->render_delay_ms = render_delay_ms_;
|
|
}
|
|
|
|
private:
|
|
mutable rtc::CriticalSection crit_;
|
|
int video_channel_id_;
|
|
int framerate_;
|
|
int bitrate_;
|
|
int decode_ms_;
|
|
int max_decode_ms_;
|
|
int current_delay_ms_;
|
|
int target_delay_ms_;
|
|
int jitter_buffer_ms_;
|
|
int min_playout_delay_ms_;
|
|
int render_delay_ms_;
|
|
};
|
|
|
|
class WebRtcEncoderObserver : public webrtc::ViEEncoderObserver {
|
|
public:
|
|
explicit WebRtcEncoderObserver(int video_channel_id)
|
|
: video_channel_id_(video_channel_id),
|
|
framerate_(0),
|
|
bitrate_(0),
|
|
suspended_(false) {
|
|
}
|
|
|
|
// virtual functions from VieEncoderObserver.
|
|
virtual void OutgoingRate(const int video_channel_id,
|
|
const unsigned int framerate,
|
|
const unsigned int bitrate) {
|
|
rtc::CritScope cs(&crit_);
|
|
ASSERT(video_channel_id_ == video_channel_id);
|
|
framerate_ = framerate;
|
|
bitrate_ = bitrate;
|
|
}
|
|
|
|
virtual void SuspendChange(int video_channel_id, bool is_suspended) {
|
|
rtc::CritScope cs(&crit_);
|
|
ASSERT(video_channel_id_ == video_channel_id);
|
|
suspended_ = is_suspended;
|
|
}
|
|
|
|
int framerate() const {
|
|
rtc::CritScope cs(&crit_);
|
|
return framerate_;
|
|
}
|
|
int bitrate() const {
|
|
rtc::CritScope cs(&crit_);
|
|
return bitrate_;
|
|
}
|
|
bool suspended() const {
|
|
rtc::CritScope cs(&crit_);
|
|
return suspended_;
|
|
}
|
|
|
|
private:
|
|
mutable rtc::CriticalSection crit_;
|
|
int video_channel_id_;
|
|
int framerate_;
|
|
int bitrate_;
|
|
bool suspended_;
|
|
};
|
|
|
|
struct CapturedFrameInfo {
|
|
CapturedFrameInfo()
|
|
: width(0),
|
|
height(0),
|
|
screencast(false),
|
|
elapsed_time(-1),
|
|
timestamp(-1) {}
|
|
CapturedFrameInfo(size_t width,
|
|
size_t height,
|
|
bool screencast,
|
|
int64_t elapsed_time,
|
|
int64_t timestamp)
|
|
: width(width),
|
|
height(height),
|
|
screencast(screencast),
|
|
elapsed_time(elapsed_time),
|
|
timestamp(timestamp) {}
|
|
|
|
size_t width;
|
|
size_t height;
|
|
bool screencast;
|
|
|
|
int64_t elapsed_time;
|
|
int64_t timestamp;
|
|
};
|
|
|
|
class WebRtcLocalStreamInfo {
|
|
public:
|
|
WebRtcLocalStreamInfo() : time_stamp_(-1) {}
|
|
int64 time_stamp() const {
|
|
rtc::CritScope cs(&crit_);
|
|
return time_stamp_;
|
|
}
|
|
int framerate() {
|
|
rtc::CritScope cs(&crit_);
|
|
return static_cast<int>(rate_tracker_.units_second());
|
|
}
|
|
|
|
void UpdateFrame(const VideoFrame* frame) {
|
|
rtc::CritScope cs(&crit_);
|
|
time_stamp_ = frame->GetTimeStamp();
|
|
rate_tracker_.Update(1);
|
|
}
|
|
|
|
private:
|
|
mutable rtc::CriticalSection crit_;
|
|
int64 time_stamp_;
|
|
rtc::RateTracker rate_tracker_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(WebRtcLocalStreamInfo);
|
|
};
|
|
|
|
// WebRtcVideoChannelRecvInfo is a container class with members such as renderer
|
|
// and a decoder observer that is used by receive channels.
|
|
// It must exist as long as the receive channel is connected to renderer or a
|
|
// decoder observer in this class and methods in the class should only be called
|
|
// from the worker thread.
|
|
class WebRtcVideoChannelRecvInfo {
|
|
public:
|
|
typedef std::map<int, webrtc::VideoDecoder*> DecoderMap; // Key: payload type
|
|
explicit WebRtcVideoChannelRecvInfo(int channel_id)
|
|
: channel_id_(channel_id),
|
|
render_adapter_(NULL, channel_id),
|
|
decoder_observer_(channel_id) {
|
|
}
|
|
int channel_id() { return channel_id_; }
|
|
void SetRenderer(VideoRenderer* renderer) {
|
|
render_adapter_.SetRenderer(renderer);
|
|
}
|
|
WebRtcRenderAdapter* render_adapter() { return &render_adapter_; }
|
|
WebRtcDecoderObserver* decoder_observer() { return &decoder_observer_; }
|
|
void RegisterDecoder(int pl_type, webrtc::VideoDecoder* decoder) {
|
|
ASSERT(!IsDecoderRegistered(pl_type));
|
|
registered_decoders_[pl_type] = decoder;
|
|
}
|
|
bool IsDecoderRegistered(int pl_type) {
|
|
return registered_decoders_.count(pl_type) != 0;
|
|
}
|
|
const DecoderMap& registered_decoders() {
|
|
return registered_decoders_;
|
|
}
|
|
void ClearRegisteredDecoders() {
|
|
registered_decoders_.clear();
|
|
}
|
|
|
|
private:
|
|
int channel_id_; // Webrtc video channel number.
|
|
// Renderer for this channel.
|
|
WebRtcRenderAdapter render_adapter_;
|
|
WebRtcDecoderObserver decoder_observer_;
|
|
DecoderMap registered_decoders_;
|
|
};
|
|
|
|
class WebRtcOveruseObserver : public webrtc::CpuOveruseObserver {
|
|
public:
|
|
explicit WebRtcOveruseObserver(CoordinatedVideoAdapter* video_adapter)
|
|
: video_adapter_(video_adapter),
|
|
enabled_(false) {
|
|
}
|
|
|
|
// TODO(mflodman): Consider sending resolution as part of event, to let
|
|
// adapter know what resolution the request is based on. Helps eliminate stale
|
|
// data, race conditions.
|
|
void OveruseDetected() override {
|
|
rtc::CritScope cs(&crit_);
|
|
if (!enabled_) {
|
|
return;
|
|
}
|
|
|
|
video_adapter_->OnCpuResolutionRequest(CoordinatedVideoAdapter::DOWNGRADE);
|
|
}
|
|
|
|
void NormalUsage() override {
|
|
rtc::CritScope cs(&crit_);
|
|
if (!enabled_) {
|
|
return;
|
|
}
|
|
|
|
video_adapter_->OnCpuResolutionRequest(CoordinatedVideoAdapter::UPGRADE);
|
|
}
|
|
|
|
void Enable(bool enable) {
|
|
LOG(LS_INFO) << "WebRtcOveruseObserver enable: " << enable;
|
|
rtc::CritScope cs(&crit_);
|
|
enabled_ = enable;
|
|
}
|
|
|
|
bool enabled() const { return enabled_; }
|
|
|
|
private:
|
|
CoordinatedVideoAdapter* video_adapter_;
|
|
bool enabled_;
|
|
rtc::CriticalSection crit_;
|
|
};
|
|
|
|
|
|
class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> {
|
|
public:
|
|
typedef std::map<int, webrtc::VideoEncoder*> EncoderMap; // Key: payload type
|
|
|
|
enum AdaptFormatType {
|
|
// This is how we make SetSendStreamFormat take precedence over
|
|
// SetSendCodecs.
|
|
kAdaptFormatTypeNone = 0, // Unset
|
|
kAdaptFormatTypeCodec = 1, // From SetSendCodec
|
|
kAdaptFormatTypeStream = 2, // From SetStreamFormat
|
|
};
|
|
|
|
WebRtcVideoChannelSendInfo(int channel_id, int capture_id,
|
|
webrtc::ViEExternalCapture* external_capture,
|
|
rtc::CpuMonitor* cpu_monitor)
|
|
: channel_id_(channel_id),
|
|
capture_id_(capture_id),
|
|
sending_(false),
|
|
muted_(false),
|
|
video_capturer_(NULL),
|
|
encoder_observer_(channel_id),
|
|
external_capture_(external_capture),
|
|
cpu_monitor_(cpu_monitor),
|
|
old_adaptation_changes_(0),
|
|
adapt_format_type_(kAdaptFormatTypeNone) {
|
|
}
|
|
|
|
int channel_id() const { return channel_id_; }
|
|
int capture_id() const { return capture_id_; }
|
|
void set_sending(bool sending) { sending_ = sending; }
|
|
bool sending() const { return sending_; }
|
|
void set_send_params(const VideoSendParams& send_params) {
|
|
send_params_ = send_params;
|
|
}
|
|
const VideoSendParams& send_params() const {
|
|
return send_params_;
|
|
}
|
|
const Settable<CapturedFrameInfo>& last_captured_frame_info() const {
|
|
return last_captured_frame_info_;
|
|
}
|
|
void set_muted(bool on) {
|
|
// TODO(asapersson): add support.
|
|
muted_ = on;
|
|
}
|
|
bool muted() {return muted_; }
|
|
|
|
WebRtcEncoderObserver* encoder_observer() { return &encoder_observer_; }
|
|
webrtc::ViEExternalCapture* external_capture() { return external_capture_; }
|
|
const VideoFormat& adapt_format() const { return adapt_format_; }
|
|
AdaptFormatType adapt_format_type() const { return adapt_format_type_; }
|
|
bool adapt_format_set() const {
|
|
return adapt_format_type() != kAdaptFormatTypeNone;
|
|
}
|
|
|
|
// Returns true if the last captured frame info changed.
|
|
void SetLastCapturedFrameInfo(
|
|
const VideoFrame* frame, bool screencast, bool* changed) {
|
|
CapturedFrameInfo last;
|
|
*changed =
|
|
!(last_captured_frame_info_.Get(&last) &&
|
|
frame->GetWidth() == last.width &&
|
|
frame->GetHeight() == last.height && screencast == last.screencast);
|
|
|
|
last_captured_frame_info_.Set(
|
|
CapturedFrameInfo(frame->GetWidth(), frame->GetHeight(), screencast,
|
|
frame->GetElapsedTime(), frame->GetTimeStamp()));
|
|
}
|
|
|
|
// Tells the video adapter to adapt down to a given format. The
|
|
// type indicates where the format came from, where different types
|
|
// have slightly different behavior and priority.
|
|
void SetAdaptFormat(const VideoFormat& format, AdaptFormatType type) {
|
|
if (type < adapt_format_type_) {
|
|
// Formats from SetSendStream format are higher priority than
|
|
// ones from SetSendCodecs wich is higher priority than not
|
|
// being set. If something lower-prioirty comes in, just ignore
|
|
// it.
|
|
return;
|
|
}
|
|
|
|
// TODO(pthatcher): Use the adapter for all max size enforcement,
|
|
// both codec-based and SetSendStreamFormat-based. For now, we
|
|
// can't do that without fixing a lot of unit tests.
|
|
if (video_adapter() && type == kAdaptFormatTypeStream) {
|
|
video_adapter()->OnOutputFormatRequest(format);
|
|
}
|
|
|
|
adapt_format_ = format;
|
|
adapt_format_type_ = type;
|
|
}
|
|
|
|
int CurrentAdaptReason() const {
|
|
if (!video_adapter()) {
|
|
return CoordinatedVideoAdapter::ADAPTREASON_NONE;
|
|
}
|
|
return video_adapter()->adapt_reason();
|
|
}
|
|
int AdaptChanges() const {
|
|
if (!video_adapter()) {
|
|
return old_adaptation_changes_;
|
|
}
|
|
return old_adaptation_changes_ + video_adapter()->adaptation_changes();
|
|
}
|
|
|
|
void set_stream_params(const StreamParams& sp) {
|
|
send_params_.stream = sp;
|
|
}
|
|
const StreamParams& stream_params() const { return send_params_.stream; }
|
|
// A default send channel can be non-active if a stream hasn't been
|
|
// added yet, or if all streams have been removed (at which point,
|
|
// Deactive is called).
|
|
bool IsActive() {
|
|
return stream_params().first_ssrc() != 0;
|
|
}
|
|
void Deactivate() {
|
|
send_params_.stream = StreamParams();
|
|
}
|
|
|
|
WebRtcLocalStreamInfo* local_stream_info() {
|
|
return &local_stream_info_;
|
|
}
|
|
VideoCapturer* video_capturer() {
|
|
return video_capturer_;
|
|
}
|
|
void set_video_capturer(VideoCapturer* video_capturer,
|
|
ViEWrapper* vie_wrapper) {
|
|
if (video_capturer == video_capturer_) {
|
|
return;
|
|
}
|
|
|
|
CoordinatedVideoAdapter* old_video_adapter = video_adapter();
|
|
if (old_video_adapter) {
|
|
// Get adaptation changes from old video adapter.
|
|
old_adaptation_changes_ += old_video_adapter->adaptation_changes();
|
|
// Disconnect signals from old video adapter.
|
|
SignalCpuAdaptationUnable.disconnect(old_video_adapter);
|
|
if (cpu_monitor_) {
|
|
cpu_monitor_->SignalUpdate.disconnect(old_video_adapter);
|
|
}
|
|
}
|
|
|
|
video_capturer_ = video_capturer;
|
|
|
|
vie_wrapper->base()->RegisterCpuOveruseObserver(channel_id_, NULL);
|
|
if (!video_capturer) {
|
|
overuse_observer_.reset();
|
|
return;
|
|
}
|
|
|
|
CoordinatedVideoAdapter* adapter = video_adapter();
|
|
ASSERT(adapter && "Video adapter should not be null here.");
|
|
|
|
// TODO(pthatcher): Use the adapter for all max size enforcement,
|
|
// both codec-based and SetSendStreamFormat-based. For now, we
|
|
// can't do that without fixing a lot of unit tests.
|
|
if (adapt_format_type_ == kAdaptFormatTypeStream) {
|
|
adapter->OnOutputFormatRequest(adapt_format_);
|
|
}
|
|
|
|
UpdateAdapterCpuOptions();
|
|
|
|
overuse_observer_.reset(new WebRtcOveruseObserver(adapter));
|
|
vie_wrapper->base()->RegisterCpuOveruseObserver(channel_id_,
|
|
overuse_observer_.get());
|
|
// (Dis)connect the video adapter from the cpu monitor as appropriate.
|
|
SetCpuOveruseDetection(
|
|
video_options_.cpu_overuse_detection.GetWithDefaultIfUnset(false));
|
|
|
|
SignalCpuAdaptationUnable.repeat(adapter->SignalCpuAdaptationUnable);
|
|
}
|
|
|
|
CoordinatedVideoAdapter* video_adapter() {
|
|
if (!video_capturer_) {
|
|
return NULL;
|
|
}
|
|
return video_capturer_->video_adapter();
|
|
}
|
|
const CoordinatedVideoAdapter* video_adapter() const {
|
|
if (!video_capturer_) {
|
|
return NULL;
|
|
}
|
|
return video_capturer_->video_adapter();
|
|
}
|
|
|
|
void ApplyCpuOptions(const VideoOptions& video_options) {
|
|
bool cpu_overuse_detection_changed =
|
|
video_options.cpu_overuse_detection.IsSet() &&
|
|
(video_options.cpu_overuse_detection.GetWithDefaultIfUnset(false) !=
|
|
video_options_.cpu_overuse_detection.GetWithDefaultIfUnset(false));
|
|
// Use video_options_.SetAll() instead of assignment so that unset value in
|
|
// video_options will not overwrite the previous option value.
|
|
video_options_.SetAll(video_options);
|
|
UpdateAdapterCpuOptions();
|
|
if (cpu_overuse_detection_changed) {
|
|
SetCpuOveruseDetection(
|
|
video_options_.cpu_overuse_detection.GetWithDefaultIfUnset(false));
|
|
}
|
|
}
|
|
|
|
void UpdateAdapterCpuOptions() {
|
|
if (!video_capturer_) {
|
|
return;
|
|
}
|
|
|
|
bool cpu_smoothing, adapt_third;
|
|
float low, med, high;
|
|
bool cpu_adapt =
|
|
video_options_.adapt_input_to_cpu_usage.GetWithDefaultIfUnset(false);
|
|
bool cpu_overuse_detection =
|
|
video_options_.cpu_overuse_detection.GetWithDefaultIfUnset(false);
|
|
|
|
// TODO(thorcarpenter): Have VideoAdapter be responsible for setting
|
|
// all these video options.
|
|
CoordinatedVideoAdapter* video_adapter = video_capturer_->video_adapter();
|
|
if (video_options_.adapt_input_to_cpu_usage.IsSet() ||
|
|
video_options_.cpu_overuse_detection.IsSet()) {
|
|
video_adapter->set_cpu_adaptation(cpu_adapt || cpu_overuse_detection);
|
|
}
|
|
if (video_options_.adapt_cpu_with_smoothing.Get(&cpu_smoothing)) {
|
|
video_adapter->set_cpu_smoothing(cpu_smoothing);
|
|
}
|
|
if (video_options_.process_adaptation_threshhold.Get(&med)) {
|
|
video_adapter->set_process_threshold(med);
|
|
}
|
|
if (video_options_.system_low_adaptation_threshhold.Get(&low)) {
|
|
video_adapter->set_low_system_threshold(low);
|
|
}
|
|
if (video_options_.system_high_adaptation_threshhold.Get(&high)) {
|
|
video_adapter->set_high_system_threshold(high);
|
|
}
|
|
if (video_options_.video_adapt_third.Get(&adapt_third)) {
|
|
video_adapter->set_scale_third(adapt_third);
|
|
}
|
|
}
|
|
|
|
void SetCpuOveruseDetection(bool enable) {
|
|
if (overuse_observer_) {
|
|
overuse_observer_->Enable(enable);
|
|
}
|
|
|
|
// The video adapter is signaled by overuse detection if enabled; otherwise
|
|
// it will be signaled by cpu monitor.
|
|
CoordinatedVideoAdapter* adapter = video_adapter();
|
|
if (adapter) {
|
|
if (cpu_monitor_) {
|
|
if (enable) {
|
|
cpu_monitor_->SignalUpdate.disconnect(adapter);
|
|
} else {
|
|
cpu_monitor_->SignalUpdate.connect(
|
|
adapter, &CoordinatedVideoAdapter::OnCpuLoadUpdated);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProcessFrame(const VideoFrame& original_frame, bool mute,
|
|
VideoFrame** processed_frame) {
|
|
if (!mute) {
|
|
*processed_frame = original_frame.Copy(); // Shallow copy.
|
|
} else {
|
|
// Cache a black frame of the same dimensions as original_frame.
|
|
if (black_frame_.GetWidth() != original_frame.GetWidth() ||
|
|
black_frame_.GetHeight() != original_frame.GetHeight()) {
|
|
black_frame_.InitToBlack(static_cast<int>(original_frame.GetWidth()),
|
|
static_cast<int>(original_frame.GetHeight()),
|
|
1, 1,
|
|
original_frame.GetElapsedTime(),
|
|
original_frame.GetTimeStamp());
|
|
}
|
|
*processed_frame = black_frame_.Copy(); // Shallow copy.
|
|
(*processed_frame)->SetElapsedTime(original_frame.GetElapsedTime());
|
|
(*processed_frame)->SetTimeStamp(original_frame.GetTimeStamp());
|
|
}
|
|
local_stream_info_.UpdateFrame(*processed_frame);
|
|
}
|
|
void RegisterEncoder(int pl_type, webrtc::VideoEncoder* encoder) {
|
|
ASSERT(!IsEncoderRegistered(pl_type));
|
|
registered_encoders_[pl_type] = encoder;
|
|
}
|
|
bool IsEncoderRegistered(int pl_type) {
|
|
return registered_encoders_.count(pl_type) != 0;
|
|
}
|
|
const EncoderMap& registered_encoders() {
|
|
return registered_encoders_;
|
|
}
|
|
void ClearRegisteredEncoders() {
|
|
registered_encoders_.clear();
|
|
}
|
|
|
|
sigslot::repeater0<> SignalCpuAdaptationUnable;
|
|
|
|
private:
|
|
int channel_id_;
|
|
int capture_id_;
|
|
VideoSendParams send_params_;
|
|
// TODO(pthatcher): Merge CapturedFrameInfo and LocalStreamInfo.
|
|
Settable<CapturedFrameInfo> last_captured_frame_info_;
|
|
bool sending_;
|
|
bool muted_;
|
|
VideoCapturer* video_capturer_;
|
|
WebRtcEncoderObserver encoder_observer_;
|
|
webrtc::ViEExternalCapture* external_capture_;
|
|
EncoderMap registered_encoders_;
|
|
|
|
WebRtcLocalStreamInfo local_stream_info_;
|
|
|
|
rtc::CpuMonitor* cpu_monitor_;
|
|
rtc::scoped_ptr<WebRtcOveruseObserver> overuse_observer_;
|
|
|
|
int old_adaptation_changes_;
|
|
|
|
VideoOptions video_options_;
|
|
|
|
VideoFormat adapt_format_;
|
|
AdaptFormatType adapt_format_type_;
|
|
WebRtcVideoFrame black_frame_; // Cached frame for mute.
|
|
};
|
|
|
|
static bool GetCpuOveruseOptions(const VideoOptions& options,
|
|
webrtc::CpuOveruseOptions* overuse_options) {
|
|
int underuse_threshold = 0;
|
|
int overuse_threshold = 0;
|
|
if (!options.cpu_underuse_threshold.Get(&underuse_threshold) ||
|
|
!options.cpu_overuse_threshold.Get(&overuse_threshold)) {
|
|
return false;
|
|
}
|
|
if (underuse_threshold <= 0 || overuse_threshold <= 0) {
|
|
return false;
|
|
}
|
|
// Valid thresholds.
|
|
bool encode_usage =
|
|
options.cpu_overuse_encode_usage.GetWithDefaultIfUnset(false);
|
|
overuse_options->enable_capture_jitter_method = !encode_usage;
|
|
overuse_options->enable_encode_usage_method = encode_usage;
|
|
if (encode_usage) {
|
|
// Use method based on encode usage.
|
|
overuse_options->low_encode_usage_threshold_percent = underuse_threshold;
|
|
overuse_options->high_encode_usage_threshold_percent = overuse_threshold;
|
|
|
|
// Set optional thresholds, if configured.
|
|
int underuse_rsd_threshold = 0;
|
|
if (options.cpu_underuse_encode_rsd_threshold.Get(
|
|
&underuse_rsd_threshold)) {
|
|
overuse_options->low_encode_time_rsd_threshold = underuse_rsd_threshold;
|
|
}
|
|
int overuse_rsd_threshold = 0;
|
|
if (options.cpu_overuse_encode_rsd_threshold.Get(&overuse_rsd_threshold)) {
|
|
overuse_options->high_encode_time_rsd_threshold = overuse_rsd_threshold;
|
|
}
|
|
} else {
|
|
// Use default method based on capture jitter.
|
|
overuse_options->low_capture_jitter_threshold_ms =
|
|
static_cast<float>(underuse_threshold);
|
|
overuse_options->high_capture_jitter_threshold_ms =
|
|
static_cast<float>(overuse_threshold);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine)
|
|
: voice_engine_(voice_engine), trace_callback_(voice_engine) {
|
|
Construct(new ViEWrapper(), new ViETraceWrapper(), voice_engine,
|
|
new rtc::CpuMonitor(NULL));
|
|
}
|
|
|
|
WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
|
|
ViEWrapper* vie_wrapper,
|
|
rtc::CpuMonitor* cpu_monitor)
|
|
: voice_engine_(voice_engine), trace_callback_(voice_engine) {
|
|
Construct(vie_wrapper, new ViETraceWrapper(), voice_engine, cpu_monitor);
|
|
}
|
|
|
|
WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
|
|
ViEWrapper* vie_wrapper,
|
|
ViETraceWrapper* tracing,
|
|
rtc::CpuMonitor* cpu_monitor)
|
|
: voice_engine_(voice_engine), trace_callback_(voice_engine) {
|
|
Construct(vie_wrapper, tracing, voice_engine, cpu_monitor);
|
|
}
|
|
|
|
void WebRtcVideoEngine::Construct(ViEWrapper* vie_wrapper,
|
|
ViETraceWrapper* tracing,
|
|
WebRtcVoiceEngine* voice_engine,
|
|
rtc::CpuMonitor* cpu_monitor) {
|
|
LOG(LS_INFO) << "WebRtcVideoEngine::WebRtcVideoEngine";
|
|
worker_thread_ = NULL;
|
|
vie_wrapper_.reset(vie_wrapper);
|
|
vie_wrapper_base_initialized_ = false;
|
|
tracing_.reset(tracing);
|
|
initialized_ = false;
|
|
SetTraceFilter(SeverityToFilter(kDefaultLogSeverity));
|
|
render_module_.reset(new WebRtcPassthroughRender());
|
|
capture_started_ = false;
|
|
decoder_factory_ = NULL;
|
|
encoder_factory_ = NULL;
|
|
cpu_monitor_.reset(cpu_monitor);
|
|
|
|
SetTraceOptions("");
|
|
if (tracing_->SetTraceCallback(&trace_callback_) != 0) {
|
|
LOG_RTCERR1(SetTraceCallback, &trace_callback_);
|
|
}
|
|
|
|
default_video_codec_list_ = DefaultVideoCodecList();
|
|
|
|
// 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.
|
|
CHECK(SetDefaultCodec(default_video_codec_list_.front()))
|
|
<< "Failed to initialize list of supported codec types.";
|
|
|
|
// Consider jitter, packet loss, etc when rendering. This will
|
|
// theoretically make rendering more smooth.
|
|
EnableTimedRender();
|
|
|
|
// Load our RTP Header extensions.
|
|
rtp_header_extensions_.push_back(
|
|
RtpHeaderExtension(kRtpTimestampOffsetHeaderExtension,
|
|
kRtpTimestampOffsetHeaderExtensionDefaultId));
|
|
rtp_header_extensions_.push_back(
|
|
RtpHeaderExtension(kRtpAbsoluteSenderTimeHeaderExtension,
|
|
kRtpAbsoluteSenderTimeHeaderExtensionDefaultId));
|
|
}
|
|
|
|
WebRtcVideoEngine::~WebRtcVideoEngine() {
|
|
LOG(LS_INFO) << "WebRtcVideoEngine::~WebRtcVideoEngine";
|
|
if (initialized_) {
|
|
Terminate();
|
|
}
|
|
|
|
tracing_->SetTraceCallback(NULL);
|
|
// Test to see if the media processor was deregistered properly.
|
|
ASSERT(SignalMediaFrame.is_empty());
|
|
}
|
|
|
|
bool WebRtcVideoEngine::Init(rtc::Thread* worker_thread) {
|
|
LOG(LS_INFO) << "WebRtcVideoEngine::Init";
|
|
worker_thread_ = worker_thread;
|
|
ASSERT(worker_thread_ != NULL);
|
|
|
|
cpu_monitor_->set_thread(worker_thread_);
|
|
if (!cpu_monitor_->Start(kCpuMonitorPeriodMs)) {
|
|
LOG(LS_ERROR) << "Failed to start CPU monitor.";
|
|
cpu_monitor_.reset();
|
|
}
|
|
|
|
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(rtc::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 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;
|
|
|
|
if (vie_wrapper_->render()->DeRegisterVideoRenderModule(
|
|
*render_module_.get()) != 0) {
|
|
LOG_RTCERR0(DeRegisterVideoRenderModule);
|
|
}
|
|
|
|
if (vie_wrapper_->base()->SetVoiceEngine(NULL) != 0) {
|
|
LOG_RTCERR0(SetVoiceEngine);
|
|
}
|
|
|
|
if (cpu_monitor_.get()) {
|
|
cpu_monitor_->Stop();
|
|
}
|
|
}
|
|
|
|
int WebRtcVideoEngine::GetCapabilities() {
|
|
return VIDEO_RECV | VIDEO_SEND;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
ASSERT(!video_codecs_.empty());
|
|
default_codec_format_ = VideoFormatFromCodec(video_codecs_[0]);
|
|
|
|
return true;
|
|
}
|
|
|
|
WebRtcVideoMediaChannel* WebRtcVideoEngine::CreateChannel(
|
|
VoiceMediaChannel* voice_channel) {
|
|
return CreateChannel(VideoOptions(), voice_channel);
|
|
}
|
|
|
|
WebRtcVideoMediaChannel* WebRtcVideoEngine::CreateChannel(
|
|
const VideoOptions& options,
|
|
VoiceMediaChannel* voice_channel) {
|
|
WebRtcVideoMediaChannel* channel =
|
|
new WebRtcVideoMediaChannel(this, voice_channel);
|
|
if (!channel->Init()) {
|
|
delete channel;
|
|
return NULL;
|
|
}
|
|
|
|
if (!channel->SetOptions(options)) {
|
|
LOG(LS_WARNING) << "Failed to set options while creating channel.";
|
|
}
|
|
return channel;
|
|
}
|
|
|
|
const std::vector<VideoCodec>& WebRtcVideoEngine::codecs() const {
|
|
return video_codecs_;
|
|
}
|
|
|
|
const std::vector<RtpHeaderExtension>&
|
|
WebRtcVideoEngine::rtp_header_extensions() const {
|
|
return rtp_header_extensions_;
|
|
}
|
|
|
|
void WebRtcVideoEngine::SetLogging(int min_sev, const char* filter) {
|
|
// if min_sev == -1, we keep the current log level.
|
|
if (min_sev >= 0) {
|
|
SetTraceFilter(SeverityToFilter(min_sev));
|
|
}
|
|
SetTraceOptions(filter);
|
|
}
|
|
|
|
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) {
|
|
if (encoder_factory_) {
|
|
const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs =
|
|
encoder_factory_->codecs();
|
|
for (size_t j = 0; j < codecs.size(); ++j) {
|
|
VideoCodec codec(GetExternalVideoPayloadType(j), codecs[j].name, 0, 0, 0,
|
|
0);
|
|
if (codec.Matches(in))
|
|
return true;
|
|
}
|
|
}
|
|
for (size_t j = 0; j != default_video_codec_list_.size(); ++j) {
|
|
if (default_video_codec_list_[j].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.
|
|
// TODO(ronghuawu): Remove |current| from the interface.
|
|
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)) {
|
|
continue;
|
|
}
|
|
|
|
out->id = requested.id;
|
|
out->name = requested.name;
|
|
out->preference = requested.preference;
|
|
out->params = requested.params;
|
|
out->framerate = std::min(requested.framerate, local_max->framerate);
|
|
out->width = 0;
|
|
out->height = 0;
|
|
out->params = requested.params;
|
|
out->feedback_params = requested.feedback_params;
|
|
|
|
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;
|
|
}
|
|
|
|
// Reduce the requested size by /= 2 until it's width under
|
|
// |local_max->width|.
|
|
out->width = requested.width;
|
|
out->height = requested.height;
|
|
while (out->width > local_max->width) {
|
|
out->width /= 2;
|
|
out->height /= 2;
|
|
}
|
|
|
|
if (out->width > 0 && out->height > 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void 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;
|
|
if (BitrateIsSet(in_codec.minBitrate)) {
|
|
out_codec->SetParam(kCodecParamMinBitrate, in_codec.minBitrate);
|
|
}
|
|
if (BitrateIsSet(in_codec.maxBitrate)) {
|
|
out_codec->SetParam(kCodecParamMaxBitrate, in_codec.maxBitrate);
|
|
}
|
|
if (BitrateIsSet(in_codec.startBitrate)) {
|
|
out_codec->SetParam(kCodecParamStartBitrate, in_codec.startBitrate);
|
|
}
|
|
if (in_codec.qpMax) {
|
|
out_codec->SetParam(kCodecParamMaxQuantization, in_codec.qpMax);
|
|
}
|
|
}
|
|
|
|
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 &&
|
|
_stricmp(in_codec.name.c_str(), out_codec->plName) == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If not found, check if this is supported by external encoder factory.
|
|
if (!found && encoder_factory_) {
|
|
const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs =
|
|
encoder_factory_->codecs();
|
|
for (size_t i = 0; i < codecs.size(); ++i) {
|
|
if (_stricmp(in_codec.name.c_str(), codecs[i].name.c_str()) == 0) {
|
|
out_codec->codecType = codecs[i].type;
|
|
out_codec->plType = GetExternalVideoPayloadType(i);
|
|
rtc::strcpyn(out_codec->plName, sizeof(out_codec->plName),
|
|
codecs[i].name.c_str(), codecs[i].name.length());
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Is this an RTX codec? Handled separately here since webrtc doesn't handle
|
|
// them as webrtc::VideoCodec internally.
|
|
if (!found && _stricmp(in_codec.name.c_str(), kRtxCodecName) == 0) {
|
|
rtc::strcpyn(out_codec->plName, sizeof(out_codec->plName),
|
|
in_codec.name.c_str(), in_codec.name.length());
|
|
out_codec->plType = in_codec.id;
|
|
found = true;
|
|
}
|
|
|
|
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;
|
|
|
|
// Convert bitrate parameters.
|
|
int max_bitrate = -1;
|
|
int min_bitrate = -1;
|
|
int start_bitrate = -1;
|
|
|
|
in_codec.GetParam(kCodecParamMinBitrate, &min_bitrate);
|
|
in_codec.GetParam(kCodecParamMaxBitrate, &max_bitrate);
|
|
in_codec.GetParam(kCodecParamStartBitrate, &start_bitrate);
|
|
|
|
|
|
out_codec->minBitrate = min_bitrate;
|
|
out_codec->startBitrate = start_bitrate;
|
|
out_codec->maxBitrate = max_bitrate;
|
|
|
|
// Convert general codec parameters.
|
|
int max_quantization = 0;
|
|
if (in_codec.GetParam(kCodecParamMaxQuantization, &max_quantization)) {
|
|
if (max_quantization < 0) {
|
|
return false;
|
|
}
|
|
out_codec->qpMax = max_quantization;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WebRtcVideoEngine::RegisterChannel(WebRtcVideoMediaChannel *channel) {
|
|
rtc::CritScope cs(&channels_crit_);
|
|
channels_.push_back(channel);
|
|
}
|
|
|
|
void WebRtcVideoEngine::UnregisterChannel(WebRtcVideoMediaChannel *channel) {
|
|
rtc::CritScope cs(&channels_crit_);
|
|
channels_.erase(std::remove(channels_.begin(), channels_.end(), channel),
|
|
channels_.end());
|
|
}
|
|
|
|
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::SetTraceFilter(int filter) {
|
|
tracing_->SetTraceFilter(filter);
|
|
}
|
|
|
|
// See https://sites.google.com/a/google.com/wavelet/
|
|
// Home/Magic-Flute--RTC-Engine-/Magic-Flute-Command-Line-Parameters
|
|
// for all supported command line setttings.
|
|
void WebRtcVideoEngine::SetTraceOptions(const std::string& options) {
|
|
// Set WebRTC trace file.
|
|
std::vector<std::string> opts;
|
|
rtc::tokenize(options, ' ', '"', '"', &opts);
|
|
std::vector<std::string>::iterator tracefile =
|
|
std::find(opts.begin(), opts.end(), "tracefile");
|
|
if (tracefile != opts.end() && ++tracefile != opts.end()) {
|
|
// Write WebRTC debug output (at same loglevel) to file
|
|
if (tracing_->SetTraceFile(tracefile->c_str()) == -1) {
|
|
LOG_RTCERR1(SetTraceFile, *tracefile);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rebuilds the codec list to be only those that are less intensive
|
|
// than the specified codec. Prefers internal codec over external with
|
|
// higher preference field.
|
|
bool WebRtcVideoEngine::RebuildCodecList(const VideoCodec& in_codec) {
|
|
if (!FindCodec(in_codec))
|
|
return false;
|
|
|
|
video_codecs_.clear();
|
|
|
|
std::set<std::string> internal_codec_names;
|
|
for (size_t i = 0; i != default_video_codec_list_.size(); ++i) {
|
|
VideoCodec codec = default_video_codec_list_[i];
|
|
codec.width = in_codec.width;
|
|
codec.height = in_codec.height;
|
|
codec.framerate = in_codec.framerate;
|
|
video_codecs_.push_back(codec);
|
|
|
|
internal_codec_names.insert(codec.name);
|
|
}
|
|
|
|
if (encoder_factory_) {
|
|
const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs =
|
|
encoder_factory_->codecs();
|
|
for (size_t i = 0; i < codecs.size(); ++i) {
|
|
bool is_internal_codec = internal_codec_names.find(codecs[i].name) !=
|
|
internal_codec_names.end();
|
|
if (!is_internal_codec) {
|
|
VideoCodec codec(
|
|
GetExternalVideoPayloadType(i),
|
|
codecs[i].name,
|
|
codecs[i].max_width,
|
|
codecs[i].max_height,
|
|
codecs[i].max_fps,
|
|
// Use negative preference on external codec to ensure the internal
|
|
// codec is preferred.
|
|
static_cast<int>(0 - i));
|
|
AddDefaultFeedbackParams(&codec);
|
|
video_codecs_.push_back(codec);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int WebRtcVideoEngine::GetNumOfChannels() {
|
|
rtc::CritScope cs(&channels_crit_);
|
|
return static_cast<int>(channels_.size());
|
|
}
|
|
|
|
void WebRtcVideoEngine::TraceCallbackImpl::Print(
|
|
webrtc::TraceLevel level, const char* trace, int length) {
|
|
rtc::LoggingSeverity sev = rtc::LS_VERBOSE;
|
|
if (level == webrtc::kTraceError || level == webrtc::kTraceCritical)
|
|
sev = rtc::LS_ERROR;
|
|
else if (level == webrtc::kTraceWarning)
|
|
sev = rtc::LS_WARNING;
|
|
else if (level == webrtc::kTraceStateInfo || level == webrtc::kTraceInfo)
|
|
sev = rtc::LS_INFO;
|
|
else if (level == webrtc::kTraceTerseInfo)
|
|
sev = rtc::LS_INFO;
|
|
|
|
// 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 (!voice_engine_ || !voice_engine_->ShouldIgnoreTrace(msg)) {
|
|
LOG_V(sev) << "webrtc: " << msg;
|
|
}
|
|
}
|
|
}
|
|
|
|
webrtc::VideoDecoder* WebRtcVideoEngine::CreateExternalDecoder(
|
|
webrtc::VideoCodecType type) {
|
|
if (!decoder_factory_) {
|
|
return NULL;
|
|
}
|
|
return decoder_factory_->CreateVideoDecoder(type);
|
|
}
|
|
|
|
void WebRtcVideoEngine::DestroyExternalDecoder(webrtc::VideoDecoder* decoder) {
|
|
ASSERT(decoder_factory_ != NULL);
|
|
if (!decoder_factory_)
|
|
return;
|
|
decoder_factory_->DestroyVideoDecoder(decoder);
|
|
}
|
|
|
|
webrtc::VideoEncoder* WebRtcVideoEngine::CreateExternalEncoder(
|
|
webrtc::VideoCodecType type,
|
|
bool* internal_source) {
|
|
ASSERT(internal_source != NULL);
|
|
if (!encoder_factory_) {
|
|
return NULL;
|
|
}
|
|
*internal_source = encoder_factory_->EncoderTypeHasInternalSource(type);
|
|
return encoder_factory_->CreateVideoEncoder(type);
|
|
}
|
|
|
|
void WebRtcVideoEngine::DestroyExternalEncoder(webrtc::VideoEncoder* encoder) {
|
|
ASSERT(encoder_factory_ != NULL);
|
|
if (!encoder_factory_)
|
|
return;
|
|
encoder_factory_->DestroyVideoEncoder(encoder);
|
|
}
|
|
|
|
bool WebRtcVideoEngine::IsExternalEncoderCodecType(
|
|
webrtc::VideoCodecType type) const {
|
|
if (!encoder_factory_)
|
|
return false;
|
|
const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs =
|
|
encoder_factory_->codecs();
|
|
std::vector<WebRtcVideoEncoderFactory::VideoCodec>::const_iterator it;
|
|
for (it = codecs.begin(); it != codecs.end(); ++it) {
|
|
if (it->type == type)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void WebRtcVideoEngine::SetExternalDecoderFactory(
|
|
WebRtcVideoDecoderFactory* decoder_factory) {
|
|
decoder_factory_ = decoder_factory;
|
|
}
|
|
|
|
void WebRtcVideoEngine::SetExternalEncoderFactory(
|
|
WebRtcVideoEncoderFactory* encoder_factory) {
|
|
if (encoder_factory_ == encoder_factory)
|
|
return;
|
|
|
|
// No matter what happens we shouldn't hold on to a stale
|
|
// WebRtcSimulcastEncoderFactory.
|
|
simulcast_encoder_factory_.reset();
|
|
|
|
if (encoder_factory) {
|
|
const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs =
|
|
encoder_factory->codecs();
|
|
if (WebRtcSimulcastEncoderFactory::UseSimulcastEncoderFactory(codecs)) {
|
|
simulcast_encoder_factory_.reset(
|
|
new WebRtcSimulcastEncoderFactory(encoder_factory));
|
|
encoder_factory = simulcast_encoder_factory_.get();
|
|
}
|
|
}
|
|
|
|
encoder_factory_ = encoder_factory;
|
|
|
|
// Rebuild codec list while reapplying the current default codec format.
|
|
VideoCodec max_codec = default_video_codec_list_[0];
|
|
max_codec.width = video_codecs_[0].width;
|
|
max_codec.height = video_codecs_[0].height;
|
|
max_codec.framerate = video_codecs_[0].framerate;
|
|
if (!RebuildCodecList(max_codec)) {
|
|
LOG(LS_ERROR) << "Failed to initialize list of supported codec types";
|
|
}
|
|
}
|
|
|
|
// WebRtcVideoMediaChannel
|
|
|
|
WebRtcVideoMediaChannel::WebRtcVideoMediaChannel(
|
|
WebRtcVideoEngine* engine,
|
|
VoiceMediaChannel* channel)
|
|
: engine_(engine),
|
|
voice_channel_(channel),
|
|
default_channel_id_(kChannelIdUnset),
|
|
nack_enabled_(true),
|
|
remb_enabled_(false),
|
|
render_started_(false),
|
|
first_receive_ssrc_(kSsrcUnset),
|
|
receiver_report_ssrc_(kSsrcUnset),
|
|
num_unsignalled_recv_channels_(0),
|
|
send_rtx_type_(-1),
|
|
send_red_type_(-1),
|
|
send_fec_type_(-1),
|
|
sending_(false),
|
|
ratio_w_(0),
|
|
ratio_h_(0) {
|
|
engine->RegisterChannel(this);
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::Init() {
|
|
const uint32 ssrc_key = 0;
|
|
bool result = CreateChannel(ssrc_key, MD_SENDRECV, &default_channel_id_);
|
|
if (!result) {
|
|
return false;
|
|
}
|
|
if (voice_channel_) {
|
|
WebRtcVoiceMediaChannel* voice_channel =
|
|
static_cast<WebRtcVoiceMediaChannel*>(voice_channel_);
|
|
if (!voice_channel->SetupSharedBandwidthEstimation(
|
|
engine()->vie()->engine(), default_channel_id_)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
WebRtcVideoMediaChannel::~WebRtcVideoMediaChannel() {
|
|
Terminate();
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::Terminate() {
|
|
SetSend(false);
|
|
SetRender(false);
|
|
|
|
while (!send_channels_.empty()) {
|
|
if (!DeleteSendChannel(send_channels_.begin()->first)) {
|
|
LOG(LS_ERROR) << "Unable to delete channel with ssrc key "
|
|
<< send_channels_.begin()->first;
|
|
ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Remove all receive streams and the default channel.
|
|
while (!recv_channels_.empty()) {
|
|
RemoveRecvStreamInternal(recv_channels_.begin()->first);
|
|
}
|
|
|
|
// Unregister the channel from the engine.
|
|
engine()->UnregisterChannel(this);
|
|
if (worker_thread()) {
|
|
worker_thread()->Clear(this);
|
|
}
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetRecvCodecs(
|
|
const std::vector<VideoCodec>& codecs) {
|
|
receive_codecs_.clear();
|
|
associated_payload_types_.clear();
|
|
for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
|
|
iter != codecs.end(); ++iter) {
|
|
if (engine()->FindCodec(*iter)) {
|
|
webrtc::VideoCodec wcodec;
|
|
if (engine()->ConvertFromCricketVideoCodec(*iter, &wcodec)) {
|
|
receive_codecs_.push_back(wcodec);
|
|
int apt;
|
|
if (iter->GetParam(cricket::kCodecParamAssociatedPayloadType, &apt)) {
|
|
if (!IsValidRtpPayloadType(apt))
|
|
return false;
|
|
associated_payload_types_[wcodec.plType] = apt;
|
|
}
|
|
}
|
|
} else {
|
|
LOG(LS_INFO) << "Unknown codec " << iter->name;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (RecvChannelMap::iterator it = recv_channels_.begin();
|
|
it != recv_channels_.end(); ++it) {
|
|
if (!SetReceiveCodecs(it->second))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetSendCodecs(
|
|
const std::vector<VideoCodec>& codecs) {
|
|
// Match with local video codec list.
|
|
std::vector<webrtc::VideoCodec> send_codecs;
|
|
VideoCodec checked_codec;
|
|
VideoCodec dummy_current; // Will be ignored by CanSendCodec.
|
|
std::map<int, int> primary_rtx_pt_mapping;
|
|
bool nack_enabled = nack_enabled_;
|
|
bool remb_enabled = remb_enabled_;
|
|
for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
|
|
iter != codecs.end(); ++iter) {
|
|
if (_stricmp(iter->name.c_str(), kRedCodecName) == 0) {
|
|
send_red_type_ = iter->id;
|
|
} else if (_stricmp(iter->name.c_str(), kUlpfecCodecName) == 0) {
|
|
send_fec_type_ = iter->id;
|
|
} else if (_stricmp(iter->name.c_str(), kRtxCodecName) == 0) {
|
|
int rtx_type = iter->id;
|
|
int rtx_primary_type = -1;
|
|
if (iter->GetParam(kCodecParamAssociatedPayloadType, &rtx_primary_type)) {
|
|
if (!IsValidRtpPayloadType(rtx_primary_type))
|
|
return false;
|
|
primary_rtx_pt_mapping[rtx_primary_type] = rtx_type;
|
|
}
|
|
} else if (engine()->CanSendCodec(*iter, dummy_current, &checked_codec)) {
|
|
webrtc::VideoCodec wcodec;
|
|
if (engine()->ConvertFromCricketVideoCodec(checked_codec, &wcodec)) {
|
|
if (send_codecs.empty()) {
|
|
nack_enabled = IsNackEnabled(checked_codec);
|
|
remb_enabled = IsRembEnabled(checked_codec);
|
|
}
|
|
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 available";
|
|
return false;
|
|
}
|
|
|
|
// Recv protection.
|
|
// Do not update if the status is same as previously configured.
|
|
if (nack_enabled_ != nack_enabled) {
|
|
for (RecvChannelMap::iterator it = recv_channels_.begin();
|
|
it != recv_channels_.end(); ++it) {
|
|
int channel_id = it->second->channel_id();
|
|
if (!SetNackFec(channel_id, send_red_type_, send_fec_type_,
|
|
nack_enabled)) {
|
|
return false;
|
|
}
|
|
if (engine_->vie()->rtp()->SetRembStatus(channel_id,
|
|
kNotSending,
|
|
remb_enabled_) != 0) {
|
|
LOG_RTCERR3(SetRembStatus, channel_id, kNotSending, remb_enabled_);
|
|
return false;
|
|
}
|
|
}
|
|
nack_enabled_ = nack_enabled;
|
|
}
|
|
|
|
// Send settings.
|
|
// Do not update if the status is same as previously configured.
|
|
if (remb_enabled_ != remb_enabled) {
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
int channel_id = iter->second->channel_id();
|
|
if (!SetNackFec(channel_id, send_red_type_, send_fec_type_,
|
|
nack_enabled_)) {
|
|
return false;
|
|
}
|
|
if (engine_->vie()->rtp()->SetRembStatus(channel_id,
|
|
remb_enabled,
|
|
remb_enabled) != 0) {
|
|
LOG_RTCERR3(SetRembStatus, channel_id, remb_enabled, remb_enabled);
|
|
return false;
|
|
}
|
|
}
|
|
remb_enabled_ = remb_enabled;
|
|
}
|
|
|
|
// Select the first matched codec.
|
|
webrtc::VideoCodec& codec(send_codecs[0]);
|
|
|
|
// Set RTX payload type if primary now active. This value will be used in
|
|
// SetSendCodec.
|
|
std::map<int, int>::const_iterator rtx_it =
|
|
primary_rtx_pt_mapping.find(codec.plType);
|
|
if (rtx_it != primary_rtx_pt_mapping.end()) {
|
|
send_rtx_type_ = rtx_it->second;
|
|
}
|
|
|
|
if (BitrateIsSet(codec.minBitrate) && BitrateIsSet(codec.maxBitrate) &&
|
|
codec.minBitrate > codec.maxBitrate) {
|
|
// TODO(pthatcher): This behavior contradicts other behavior in
|
|
// this file which will cause min > max to push the min down to
|
|
// the max. There are unit tests for both behaviors. We should
|
|
// pick one and do that.
|
|
LOG(LS_INFO) << "Rejecting codec with min bitrate ("
|
|
<< codec.minBitrate << ") larger than max ("
|
|
<< codec.maxBitrate << "). ";
|
|
return false;
|
|
}
|
|
|
|
if (!SetSendCodec(codec)) {
|
|
return false;
|
|
}
|
|
|
|
LogSendCodecChange("SetSendCodecs()");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::MaybeRegisterExternalEncoder(
|
|
WebRtcVideoChannelSendInfo* send_channel,
|
|
const webrtc::VideoCodec& codec) {
|
|
// Codec type not supported or encoder already registered, so
|
|
// nothing to do.
|
|
if (!engine()->IsExternalEncoderCodecType(codec.codecType) ||
|
|
send_channel->IsEncoderRegistered(codec.plType)) {
|
|
return true;
|
|
}
|
|
|
|
bool internal_source;
|
|
webrtc::VideoEncoder* encoder =
|
|
engine()->CreateExternalEncoder(codec.codecType, &internal_source);
|
|
if (!encoder) {
|
|
// No external encoder created, so nothing to do.
|
|
return true;
|
|
}
|
|
|
|
const int channel_id = send_channel->channel_id();
|
|
if (engine()->vie()->ext_codec()->RegisterExternalSendCodec(
|
|
channel_id, codec.plType, encoder, internal_source) != 0) {
|
|
LOG_RTCERR2(RegisterExternalSendCodec, channel_id, codec.plName);
|
|
engine()->DestroyExternalEncoder(encoder);
|
|
return false;
|
|
}
|
|
|
|
send_channel->RegisterEncoder(codec.plType, encoder);
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::GetSendCodec(VideoCodec* send_codec) {
|
|
if (!send_codec_) {
|
|
return false;
|
|
}
|
|
ConvertToCricketVideoCodec(*send_codec_, send_codec);
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetSendStreamFormat(uint32 ssrc,
|
|
const VideoFormat& format) {
|
|
WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrc(ssrc);
|
|
if (!send_channel) {
|
|
LOG(LS_ERROR) << "The specified ssrc " << ssrc << " is not in use.";
|
|
return false;
|
|
}
|
|
|
|
send_channel->SetAdaptFormat(
|
|
format, WebRtcVideoChannelSendInfo::kAdaptFormatTypeStream);
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetRender(bool render) {
|
|
if (render == render_started_) {
|
|
return true; // no action required
|
|
}
|
|
|
|
bool ret = true;
|
|
for (RecvChannelMap::iterator it = recv_channels_.begin();
|
|
it != recv_channels_.end(); ++it) {
|
|
if (render) {
|
|
if (engine()->vie()->render()->StartRender(
|
|
it->second->channel_id()) != 0) {
|
|
LOG_RTCERR1(StartRender, it->second->channel_id());
|
|
ret = false;
|
|
}
|
|
} else {
|
|
if (engine()->vie()->render()->StopRender(
|
|
it->second->channel_id()) != 0) {
|
|
LOG_RTCERR1(StopRender, it->second->channel_id());
|
|
ret = false;
|
|
}
|
|
}
|
|
}
|
|
if (ret) {
|
|
render_started_ = render;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetSend(bool send) {
|
|
if (!HasReadySendChannels() && send) {
|
|
LOG(LS_ERROR) << "No stream added";
|
|
return false;
|
|
}
|
|
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_) {
|
|
return false;
|
|
}
|
|
// Start send now.
|
|
if (!StartSend()) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// We've been asked to stop sending.
|
|
if (!StopSend()) {
|
|
return false;
|
|
}
|
|
}
|
|
sending_ = send;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::AddSendStream(const StreamParams& sp) {
|
|
if (sp.first_ssrc() == 0) {
|
|
LOG(LS_ERROR) << "AddSendStream with 0 ssrc is not supported.";
|
|
return false;
|
|
}
|
|
|
|
LOG(LS_INFO) << "AddSendStream " << sp.ToString();
|
|
|
|
if (!IsOneSsrcStream(sp) && !IsSimulcastStream(sp)) {
|
|
LOG(LS_ERROR) << "AddSendStream: bad local stream parameters";
|
|
return false;
|
|
}
|
|
|
|
uint32 ssrc_key;
|
|
if (!CreateSendChannelSsrcKey(sp.first_ssrc(), &ssrc_key)) {
|
|
LOG(LS_ERROR) << "Trying to register duplicate ssrc: " << sp.first_ssrc();
|
|
return false;
|
|
}
|
|
// If the default channel is already used for sending create a new channel
|
|
// otherwise use the default channel for sending.
|
|
int channel_id = kChannelIdUnset;
|
|
if (!DefaultSendChannelIsActive()) {
|
|
channel_id = default_channel_id_;
|
|
} else {
|
|
if (!CreateChannel(ssrc_key, MD_SEND, &channel_id)) {
|
|
LOG(LS_ERROR) << "AddSendStream: unable to create channel";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrcKey(ssrc_key);
|
|
// If there are multiple send SSRCs, we can only set the first one here, and
|
|
// the rest of the SSRC(s) need to be set after SetSendCodec has been called
|
|
if (!SetLimitedNumberOfSendSsrcs(channel_id, sp, 1)) {
|
|
return false;
|
|
}
|
|
|
|
// Set RTCP CName.
|
|
if (engine()->vie()->rtp()->SetRTCPCName(channel_id,
|
|
sp.cname.c_str()) != 0) {
|
|
LOG_RTCERR2(SetRTCPCName, channel_id, sp.cname.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Use the SSRC of the default channel in the RTCP receiver reports.
|
|
if (IsDefaultChannelId(channel_id)) {
|
|
SetReceiverReportSsrc(sp.first_ssrc());
|
|
}
|
|
|
|
if (send_codec_) {
|
|
send_channel->SetAdaptFormat(
|
|
VideoFormatFromVieCodec(*send_codec_),
|
|
WebRtcVideoChannelSendInfo::kAdaptFormatTypeCodec);
|
|
|
|
VideoSendParams send_params;
|
|
send_params.codec = *send_codec_;
|
|
send_params.stream = sp;
|
|
if (!SetSendParams(send_channel, send_params)) {
|
|
return false;
|
|
}
|
|
LogSendCodecChange("AddStream()");
|
|
} else {
|
|
// Save the stream params for later, when we have a codec.
|
|
send_channel->set_stream_params(sp);
|
|
}
|
|
|
|
if (sending_) {
|
|
return StartSend(send_channel);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::RemoveSendStream(uint32 ssrc) {
|
|
if (ssrc == 0) {
|
|
LOG(LS_ERROR) << "RemoveSendStream with 0 ssrc is not supported.";
|
|
return false;
|
|
}
|
|
|
|
uint32 ssrc_key;
|
|
if (!GetSendChannelSsrcKey(ssrc, &ssrc_key)) {
|
|
LOG(LS_WARNING) << "Try to remove stream with ssrc " << ssrc
|
|
<< " which doesn't exist.";
|
|
return false;
|
|
}
|
|
WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrcKey(ssrc_key);
|
|
int channel_id = send_channel->channel_id();
|
|
if (IsDefaultChannelId(channel_id) && !send_channel->IsActive()) {
|
|
// Default channel will still exist. However, there is no stream
|
|
// to remove.
|
|
return false;
|
|
}
|
|
if (sending_) {
|
|
StopSend(send_channel);
|
|
}
|
|
|
|
const WebRtcVideoChannelSendInfo::EncoderMap& encoder_map =
|
|
send_channel->registered_encoders();
|
|
for (WebRtcVideoChannelSendInfo::EncoderMap::const_iterator it =
|
|
encoder_map.begin(); it != encoder_map.end(); ++it) {
|
|
if (engine()->vie()->ext_codec()->DeRegisterExternalSendCodec(
|
|
channel_id, it->first) != 0) {
|
|
LOG_RTCERR1(DeregisterEncoderObserver, channel_id);
|
|
}
|
|
engine()->DestroyExternalEncoder(it->second);
|
|
}
|
|
send_channel->ClearRegisteredEncoders();
|
|
|
|
// The receive channels depend on the default channel, recycle it instead.
|
|
if (IsDefaultChannelId(channel_id)) {
|
|
SetCapturer(GetDefaultSendChannelSsrc(), NULL);
|
|
send_channel->Deactivate();
|
|
} else {
|
|
return DeleteSendChannel(ssrc_key);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::AddRecvStream(const StreamParams& sp) {
|
|
if (sp.first_ssrc() == 0) {
|
|
LOG(LS_ERROR) << "AddRecvStream with 0 ssrc is not supported.";
|
|
return false;
|
|
}
|
|
|
|
// TODO(zhurunz) Remove this once BWE works properly across different send
|
|
// and receive channels.
|
|
// Reuse default channel for recv stream in 1:1 call.
|
|
if (!ConferenceModeIsEnabled() && first_receive_ssrc_ == kSsrcUnset) {
|
|
LOG(LS_INFO) << "Recv stream " << sp.first_ssrc()
|
|
<< " reuse default channel #"
|
|
<< default_channel_id_;
|
|
first_receive_ssrc_ = sp.first_ssrc();
|
|
if (!MaybeSetRtxSsrc(sp, default_channel_id_)) {
|
|
return false;
|
|
}
|
|
if (render_started_) {
|
|
if (engine()->vie()->render()->StartRender(default_channel_id_) !=0) {
|
|
LOG_RTCERR1(StartRender, default_channel_id_);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int channel_id = kChannelIdUnset;
|
|
uint32 ssrc = sp.first_ssrc();
|
|
WebRtcVideoChannelRecvInfo* recv_channel = GetRecvChannelBySsrc(ssrc);
|
|
if (!recv_channel && first_receive_ssrc_ != ssrc) {
|
|
// TODO(perkj): Implement recv media from multiple media SSRCs per stream.
|
|
// NOTE: We have two SSRCs per stream when RTX is enabled.
|
|
if (!IsOneSsrcStream(sp)) {
|
|
LOG(LS_ERROR) << "WebRtcVideoMediaChannel supports one primary SSRC per"
|
|
<< " stream and one FID SSRC per primary SSRC.";
|
|
return false;
|
|
}
|
|
|
|
// Create a new channel for receiving video data.
|
|
// In order to get the bandwidth estimation work fine for
|
|
// receive only channels, we connect all receiving channels
|
|
// to our master send channel.
|
|
if (!CreateChannel(sp.first_ssrc(), MD_RECV, &channel_id)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// Already exists.
|
|
if (first_receive_ssrc_ == ssrc) {
|
|
return false;
|
|
}
|
|
// Early receive added channel.
|
|
channel_id = recv_channel->channel_id();
|
|
}
|
|
|
|
if (!MaybeSetRtxSsrc(sp, channel_id)) {
|
|
return false;
|
|
}
|
|
|
|
LOG(LS_INFO) << "New video stream " << sp.first_ssrc()
|
|
<< " registered to VideoEngine channel #"
|
|
<< channel_id << " and connected to channel #"
|
|
<< default_channel_id_;
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::MaybeSetRtxSsrc(const StreamParams& sp,
|
|
int channel_id) {
|
|
uint32 rtx_ssrc;
|
|
bool has_rtx = sp.GetFidSsrc(sp.first_ssrc(), &rtx_ssrc);
|
|
if (has_rtx) {
|
|
LOG(LS_INFO) << "Setting rtx ssrc " << rtx_ssrc << " for stream "
|
|
<< sp.first_ssrc();
|
|
if (engine()->vie()->rtp()->SetRemoteSSRCType(
|
|
channel_id, webrtc::kViEStreamTypeRtx, rtx_ssrc) != 0) {
|
|
LOG_RTCERR3(SetRemoteSSRCType, channel_id, webrtc::kViEStreamTypeRtx,
|
|
rtx_ssrc);
|
|
return false;
|
|
}
|
|
rtx_to_primary_ssrc_[rtx_ssrc] = sp.first_ssrc();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::RemoveRecvStream(uint32 ssrc) {
|
|
if (ssrc == 0) {
|
|
LOG(LS_ERROR) << "RemoveRecvStream with 0 ssrc is not supported.";
|
|
return false;
|
|
}
|
|
return RemoveRecvStreamInternal(ssrc);
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::RemoveRecvStreamInternal(uint32 ssrc) {
|
|
// First remove the RTX SSRC mapping, to include this step even if exiting in
|
|
// the default receive channel logic below.
|
|
SsrcMap::iterator rtx_it = rtx_to_primary_ssrc_.begin();
|
|
while (rtx_it != rtx_to_primary_ssrc_.end()) {
|
|
if (rtx_it->second == ssrc) {
|
|
rtx_to_primary_ssrc_.erase(rtx_it++);
|
|
} else {
|
|
++rtx_it;
|
|
}
|
|
}
|
|
|
|
WebRtcVideoChannelRecvInfo* recv_channel = GetRecvChannelBySsrc(ssrc);
|
|
if (!recv_channel) {
|
|
// TODO(perkj): Remove this once BWE works properly across different send
|
|
// and receive channels.
|
|
// The default channel is reused for recv stream in 1:1 call.
|
|
if (first_receive_ssrc_ == ssrc) {
|
|
first_receive_ssrc_ = kSsrcUnset;
|
|
// Need to stop the renderer and remove it since the render window can be
|
|
// deleted after this.
|
|
if (render_started_) {
|
|
if (engine()->vie()->render()->StopRender(default_channel_id_) !=0) {
|
|
LOG_RTCERR1(StopRender, recv_channel->channel_id());
|
|
}
|
|
}
|
|
GetDefaultRecvChannel()->SetRenderer(NULL);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int channel_id = recv_channel->channel_id();
|
|
if (engine()->vie()->render()->RemoveRenderer(channel_id) != 0) {
|
|
LOG_RTCERR1(RemoveRenderer, channel_id);
|
|
}
|
|
|
|
if (engine()->vie()->network()->DeregisterSendTransport(channel_id) !=0) {
|
|
LOG_RTCERR1(DeRegisterSendTransport, channel_id);
|
|
}
|
|
|
|
if (engine()->vie()->codec()->DeregisterDecoderObserver(
|
|
channel_id) != 0) {
|
|
LOG_RTCERR1(DeregisterDecoderObserver, channel_id);
|
|
}
|
|
|
|
const WebRtcVideoChannelRecvInfo::DecoderMap& decoder_map =
|
|
recv_channel->registered_decoders();
|
|
for (WebRtcVideoChannelRecvInfo::DecoderMap::const_iterator it =
|
|
decoder_map.begin(); it != decoder_map.end(); ++it) {
|
|
if (engine()->vie()->ext_codec()->DeRegisterExternalReceiveCodec(
|
|
channel_id, it->first) != 0) {
|
|
LOG_RTCERR1(DeregisterDecoderObserver, channel_id);
|
|
}
|
|
engine()->DestroyExternalDecoder(it->second);
|
|
}
|
|
recv_channel->ClearRegisteredDecoders();
|
|
|
|
LOG(LS_INFO) << "Removing video stream " << ssrc
|
|
<< " with VideoEngine channel #"
|
|
<< channel_id;
|
|
bool ret = true;
|
|
if (engine()->vie()->base()->DeleteChannel(channel_id) == -1) {
|
|
LOG_RTCERR1(DeleteChannel, channel_id);
|
|
ret = false;
|
|
}
|
|
// Delete the WebRtcVideoChannelRecvInfo pointed to by it->second.
|
|
delete recv_channel;
|
|
recv_channels_.erase(ssrc);
|
|
return ret;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::StartSend() {
|
|
bool success = true;
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
if (!StartSend(send_channel)) {
|
|
success = false;
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::StartSend(
|
|
WebRtcVideoChannelSendInfo* send_channel) {
|
|
const int channel_id = send_channel->channel_id();
|
|
if (engine()->vie()->base()->StartSend(channel_id) != 0) {
|
|
LOG_RTCERR1(StartSend, channel_id);
|
|
return false;
|
|
}
|
|
|
|
send_channel->set_sending(true);
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::StopSend() {
|
|
bool success = true;
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
if (!StopSend(send_channel)) {
|
|
success = false;
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::StopSend(
|
|
WebRtcVideoChannelSendInfo* send_channel) {
|
|
const int channel_id = send_channel->channel_id();
|
|
if (engine()->vie()->base()->StopSend(channel_id) != 0) {
|
|
LOG_RTCERR1(StopSend, channel_id);
|
|
return false;
|
|
}
|
|
send_channel->set_sending(false);
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SendIntraFrame() {
|
|
bool success = true;
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end();
|
|
++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
const int channel_id = send_channel->channel_id();
|
|
if (engine()->vie()->codec()->SendKeyFrame(channel_id) != 0) {
|
|
LOG_RTCERR1(SendKeyFrame, channel_id);
|
|
success = false;
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::HasReadySendChannels() {
|
|
return !send_channels_.empty() &&
|
|
((send_channels_.size() > 1) || DefaultSendChannelIsActive());
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::DefaultSendChannelIsActive() {
|
|
return GetDefaultSendChannel() && GetDefaultSendChannel()->IsActive();
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::GetSendChannelSsrcKey(uint32 local_ssrc,
|
|
uint32* ssrc_key) {
|
|
*ssrc_key = kDefaultChannelSsrcKey;
|
|
// If a send channel is not ready to send it will not have local_ssrc
|
|
// registered to it.
|
|
if (!HasReadySendChannels()) {
|
|
return false;
|
|
}
|
|
// The default channel is stored with ssrc key
|
|
// kDefaultChannelSsrcKey. The ssrc key therefore does not match the
|
|
// SSRC associated with the default channel. Check if the SSRC
|
|
// provided corresponds to the default channel's SSRC.
|
|
if (local_ssrc == GetDefaultSendChannelSsrc()) {
|
|
return true;
|
|
}
|
|
if (!GetSendChannelBySsrcKey(local_ssrc)) {
|
|
// If a stream has multiple ssrcs, the local_ssrc could be any of
|
|
// them, but we use the first one (StreamParams::first_ssrc()) as
|
|
// the key.
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
if (send_channel->stream_params().has_ssrc(local_ssrc)) {
|
|
*ssrc_key = iter->first;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// The ssrc key was found in the above std::map::find call. This
|
|
// means that the ssrc is the ssrc key.
|
|
*ssrc_key = local_ssrc;
|
|
return true;
|
|
}
|
|
|
|
WebRtcVideoChannelSendInfo* WebRtcVideoMediaChannel::GetDefaultSendChannel() {
|
|
return GetSendChannelBySsrcKey(kDefaultChannelSsrcKey);
|
|
}
|
|
|
|
WebRtcVideoChannelSendInfo* WebRtcVideoMediaChannel::GetSendChannelBySsrcKey(
|
|
uint32 ssrc_key) {
|
|
std::map<uint32, WebRtcVideoChannelSendInfo *>::iterator iter =
|
|
send_channels_.find(ssrc_key);
|
|
if (iter == send_channels_.end()) {
|
|
return NULL;
|
|
}
|
|
return iter->second;
|
|
}
|
|
|
|
WebRtcVideoChannelSendInfo* WebRtcVideoMediaChannel::GetSendChannelBySsrc(
|
|
uint32 local_ssrc) {
|
|
uint32 ssrc_key;
|
|
if (!GetSendChannelSsrcKey(local_ssrc, &ssrc_key)) {
|
|
return NULL;
|
|
}
|
|
return send_channels_[ssrc_key];
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::CreateSendChannelSsrcKey(uint32 local_ssrc,
|
|
uint32* ssrc_key) {
|
|
if (GetSendChannelSsrcKey(local_ssrc, ssrc_key)) {
|
|
// If there is an ssrc key corresponding to |local_ssrc|, the SSRC
|
|
// is already in use. SSRCs need to be unique in a session and at
|
|
// this point a duplicate SSRC has been detected.
|
|
return false;
|
|
}
|
|
if (!DefaultSendChannelIsActive()) {
|
|
// |ssrc_key| should be kDefaultChannelSsrcKey here as the default
|
|
// channel should be re-used whenever it is not used.
|
|
*ssrc_key = kDefaultChannelSsrcKey;
|
|
return true;
|
|
}
|
|
// SSRC is currently not in use and the default channel is already
|
|
// in use. Use the SSRC as ssrc_key since it is supposed to be
|
|
// unique in a session.
|
|
*ssrc_key = local_ssrc;
|
|
return true;
|
|
}
|
|
|
|
int WebRtcVideoMediaChannel::GetSendChannelNum(VideoCapturer* capturer) {
|
|
int num = 0;
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
if (send_channel->video_capturer() == capturer) {
|
|
++num;
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
uint32 WebRtcVideoMediaChannel::GetDefaultSendChannelSsrc() {
|
|
return GetDefaultSendChannel()->stream_params().first_ssrc();
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::DeleteSendChannel(uint32 ssrc_key) {
|
|
WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrcKey(ssrc_key);
|
|
if (!send_channel) {
|
|
return false;
|
|
}
|
|
MaybeDisconnectCapturer(send_channel->video_capturer());
|
|
send_channel->set_video_capturer(NULL, engine()->vie());
|
|
|
|
int channel_id = send_channel->channel_id();
|
|
int capture_id = send_channel->capture_id();
|
|
if (engine()->vie()->codec()->DeregisterEncoderObserver(
|
|
channel_id) != 0) {
|
|
LOG_RTCERR1(DeregisterEncoderObserver, channel_id);
|
|
}
|
|
|
|
// Destroy the external capture interface.
|
|
if (engine()->vie()->capture()->DisconnectCaptureDevice(
|
|
channel_id) != 0) {
|
|
LOG_RTCERR1(DisconnectCaptureDevice, channel_id);
|
|
}
|
|
if (engine()->vie()->capture()->ReleaseCaptureDevice(
|
|
capture_id) != 0) {
|
|
LOG_RTCERR1(ReleaseCaptureDevice, capture_id);
|
|
}
|
|
|
|
// The default channel is stored in both |send_channels_| and
|
|
// |recv_channels_|. To make sure it is only deleted once from vie let the
|
|
// delete call happen when tearing down |recv_channels_| and not here.
|
|
if (!IsDefaultChannelId(channel_id)) {
|
|
engine_->vie()->base()->DeleteChannel(channel_id);
|
|
}
|
|
delete send_channel;
|
|
send_channels_.erase(ssrc_key);
|
|
return true;
|
|
}
|
|
|
|
WebRtcVideoChannelRecvInfo* WebRtcVideoMediaChannel::GetDefaultRecvChannel() {
|
|
return GetRecvChannelBySsrc(kDefaultChannelSsrcKey);
|
|
}
|
|
|
|
WebRtcVideoChannelRecvInfo* WebRtcVideoMediaChannel::GetRecvChannelBySsrc(
|
|
uint32 ssrc) {
|
|
if (recv_channels_.find(ssrc) == recv_channels_.end()) {
|
|
return NULL;
|
|
}
|
|
return recv_channels_[ssrc];
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::RemoveCapturer(uint32 ssrc) {
|
|
WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrc(ssrc);
|
|
if (!send_channel) {
|
|
return false;
|
|
}
|
|
VideoCapturer* capturer = send_channel->video_capturer();
|
|
if (!capturer) {
|
|
return false;
|
|
}
|
|
MaybeDisconnectCapturer(capturer);
|
|
send_channel->set_video_capturer(NULL, engine()->vie());
|
|
const int64 timestamp = send_channel->local_stream_info()->time_stamp();
|
|
if (send_codec_) {
|
|
QueueBlackFrame(ssrc, timestamp,
|
|
VideoFormat::FpsToInterval(send_codec_->maxFramerate));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetRenderer(uint32 ssrc,
|
|
VideoRenderer* renderer) {
|
|
WebRtcVideoChannelRecvInfo* recv_channel = GetRecvChannelBySsrc(ssrc);
|
|
if (!recv_channel) {
|
|
// TODO(perkj): Remove this once BWE works properly across different send
|
|
// and receive channels.
|
|
// The default channel is reused for recv stream in 1:1 call.
|
|
if (first_receive_ssrc_ == ssrc && GetDefaultRecvChannel()) {
|
|
LOG(LS_INFO) << "SetRenderer " << ssrc
|
|
<< " reuse default channel #"
|
|
<< default_channel_id_;
|
|
GetDefaultRecvChannel()->SetRenderer(renderer);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
recv_channel->SetRenderer(renderer);
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::GetStats(VideoMediaInfo* info) {
|
|
// Get sender statistics and build VideoSenderInfo.
|
|
unsigned int total_bitrate_sent = 0;
|
|
unsigned int video_bitrate_sent = 0;
|
|
unsigned int fec_bitrate_sent = 0;
|
|
unsigned int nack_bitrate_sent = 0;
|
|
unsigned int estimated_send_bandwidth = 0;
|
|
unsigned int target_enc_bitrate = 0;
|
|
if (send_codec_) {
|
|
for (SendChannelMap::const_iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
const int channel_id = send_channel->channel_id();
|
|
VideoSenderInfo sinfo;
|
|
if (!send_channel->IsActive()) {
|
|
// This should only happen if the default vie channel is not in use.
|
|
// This can happen if no streams have ever been added or the stream
|
|
// corresponding to the default channel has been removed. Note that
|
|
// there may be non-default vie channels in use when this happen so
|
|
// asserting send_channels_.size() == 1 is not correct and neither is
|
|
// breaking out of the loop.
|
|
ASSERT(channel_id == default_channel_id_);
|
|
continue;
|
|
}
|
|
size_t bytes_sent, bytes_recv;
|
|
unsigned int packets_sent, packets_recv;
|
|
if (engine_->vie()->rtp()->GetRTPStatistics(channel_id, bytes_sent,
|
|
packets_sent, bytes_recv,
|
|
packets_recv) != 0) {
|
|
LOG_RTCERR1(GetRTPStatistics, default_channel_id_);
|
|
continue;
|
|
}
|
|
WebRtcLocalStreamInfo* channel_stream_info =
|
|
send_channel->local_stream_info();
|
|
|
|
const StreamParams& sp = send_channel->stream_params();
|
|
for (size_t i = 0; i < sp.ssrcs.size(); ++i) {
|
|
sinfo.add_ssrc(sp.ssrcs[i]);
|
|
}
|
|
sinfo.codec_name = 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.rtt_ms = -1;
|
|
|
|
VideoCapturer* video_capturer = send_channel->video_capturer();
|
|
if (video_capturer) {
|
|
VideoFormat last_captured_frame_format;
|
|
video_capturer->GetStats(&sinfo.adapt_frame_drops,
|
|
&sinfo.effects_frame_drops,
|
|
&sinfo.capturer_frame_time,
|
|
&last_captured_frame_format);
|
|
sinfo.input_frame_width = last_captured_frame_format.width;
|
|
sinfo.input_frame_height = last_captured_frame_format.height;
|
|
} else {
|
|
sinfo.input_frame_width = 0;
|
|
sinfo.input_frame_height = 0;
|
|
}
|
|
|
|
webrtc::VideoCodec vie_codec;
|
|
if (!video_capturer || video_capturer->IsMuted()) {
|
|
sinfo.send_frame_width = 0;
|
|
sinfo.send_frame_height = 0;
|
|
} else if (engine()->vie()->codec()->GetSendCodec(channel_id,
|
|
vie_codec) == 0) {
|
|
sinfo.send_frame_width = vie_codec.width;
|
|
sinfo.send_frame_height = vie_codec.height;
|
|
} else {
|
|
sinfo.send_frame_width = -1;
|
|
sinfo.send_frame_height = -1;
|
|
LOG_RTCERR1(GetSendCodec, channel_id);
|
|
}
|
|
sinfo.framerate_input = channel_stream_info->framerate();
|
|
sinfo.framerate_sent = send_channel->encoder_observer()->framerate();
|
|
sinfo.nominal_bitrate = send_channel->encoder_observer()->bitrate();
|
|
if (send_codec_) {
|
|
sinfo.preferred_bitrate = GetBitrate(
|
|
send_codec_->maxBitrate, kMaxVideoBitrate);
|
|
}
|
|
sinfo.adapt_reason = send_channel->CurrentAdaptReason();
|
|
sinfo.adapt_changes = send_channel->AdaptChanges();
|
|
|
|
webrtc::CpuOveruseMetrics metrics;
|
|
engine()->vie()->base()->GetCpuOveruseMetrics(channel_id, &metrics);
|
|
sinfo.capture_jitter_ms = metrics.capture_jitter_ms;
|
|
sinfo.avg_encode_ms = metrics.avg_encode_time_ms;
|
|
sinfo.encode_usage_percent = metrics.encode_usage_percent;
|
|
sinfo.capture_queue_delay_ms_per_s = metrics.capture_queue_delay_ms_per_s;
|
|
|
|
webrtc::RtcpPacketTypeCounter rtcp_sent;
|
|
webrtc::RtcpPacketTypeCounter rtcp_received;
|
|
if (engine()->vie()->rtp()->GetRtcpPacketTypeCounters(
|
|
channel_id, &rtcp_sent, &rtcp_received) == 0) {
|
|
sinfo.firs_rcvd = rtcp_received.fir_packets;
|
|
sinfo.plis_rcvd = rtcp_received.pli_packets;
|
|
sinfo.nacks_rcvd = rtcp_received.nack_packets;
|
|
} else {
|
|
sinfo.firs_rcvd = -1;
|
|
sinfo.plis_rcvd = -1;
|
|
sinfo.nacks_rcvd = -1;
|
|
LOG_RTCERR1(GetRtcpPacketTypeCounters, channel_id);
|
|
}
|
|
|
|
// Get received RTCP statistics for the sender (reported by the remote
|
|
// client in a RTCP packet), if available.
|
|
// It's not a fatal error if we can't, since RTCP may not have arrived
|
|
// yet.
|
|
webrtc::RtcpStatistics outgoing_stream_rtcp_stats;
|
|
int64_t outgoing_stream_rtt_ms;
|
|
|
|
if (engine_->vie()->rtp()->GetSendChannelRtcpStatistics(
|
|
channel_id,
|
|
outgoing_stream_rtcp_stats,
|
|
outgoing_stream_rtt_ms) == 0) {
|
|
// Convert Q8 to float.
|
|
sinfo.packets_lost = outgoing_stream_rtcp_stats.cumulative_lost;
|
|
sinfo.fraction_lost = static_cast<float>(
|
|
outgoing_stream_rtcp_stats.fraction_lost) / (1 << 8);
|
|
sinfo.rtt_ms = outgoing_stream_rtt_ms;
|
|
}
|
|
info->senders.push_back(sinfo);
|
|
|
|
unsigned int channel_total_bitrate_sent = 0;
|
|
unsigned int channel_video_bitrate_sent = 0;
|
|
unsigned int channel_fec_bitrate_sent = 0;
|
|
unsigned int channel_nack_bitrate_sent = 0;
|
|
if (engine_->vie()->rtp()->GetBandwidthUsage(
|
|
channel_id, channel_total_bitrate_sent, channel_video_bitrate_sent,
|
|
channel_fec_bitrate_sent, channel_nack_bitrate_sent) == 0) {
|
|
total_bitrate_sent += channel_total_bitrate_sent;
|
|
video_bitrate_sent += channel_video_bitrate_sent;
|
|
fec_bitrate_sent += channel_fec_bitrate_sent;
|
|
nack_bitrate_sent += channel_nack_bitrate_sent;
|
|
} else {
|
|
LOG_RTCERR1(GetBandwidthUsage, channel_id);
|
|
}
|
|
|
|
unsigned int target_enc_stream_bitrate = 0;
|
|
if (engine_->vie()->codec()->GetCodecTargetBitrate(
|
|
channel_id, &target_enc_stream_bitrate) == 0) {
|
|
target_enc_bitrate += target_enc_stream_bitrate;
|
|
} else {
|
|
LOG_RTCERR1(GetCodecTargetBitrate, channel_id);
|
|
}
|
|
}
|
|
if (!send_channels_.empty()) {
|
|
// GetEstimatedSendBandwidth returns the estimated bandwidth for all video
|
|
// engine channels in a channel group. Any valid channel id will do as it
|
|
// is only used to access the right group of channels.
|
|
const int channel_id = send_channels_.begin()->second->channel_id();
|
|
// Get the send bandwidth available for this MediaChannel.
|
|
if (engine_->vie()->rtp()->GetEstimatedSendBandwidth(
|
|
channel_id, &estimated_send_bandwidth) != 0) {
|
|
LOG_RTCERR1(GetEstimatedSendBandwidth, channel_id);
|
|
}
|
|
}
|
|
} else {
|
|
LOG(LS_WARNING) << "GetStats: sender information not ready.";
|
|
}
|
|
|
|
// Get the SSRC and stats for each receiver, based on our own calculations.
|
|
for (RecvChannelMap::const_iterator it = recv_channels_.begin();
|
|
it != recv_channels_.end(); ++it) {
|
|
WebRtcVideoChannelRecvInfo* channel = it->second;
|
|
|
|
unsigned int ssrc = 0;
|
|
// Get receiver statistics and build VideoReceiverInfo, if we have data.
|
|
// Skip the default channel (ssrc == 0).
|
|
if (engine_->vie()->rtp()->GetRemoteSSRC(
|
|
channel->channel_id(), ssrc) != 0 ||
|
|
ssrc == 0)
|
|
continue;
|
|
|
|
webrtc::StreamDataCounters sent;
|
|
webrtc::StreamDataCounters received;
|
|
if (engine_->vie()->rtp()->GetRtpStatistics(channel->channel_id(),
|
|
sent, received) != 0) {
|
|
LOG_RTCERR1(GetRTPStatistics, channel->channel_id());
|
|
return false;
|
|
}
|
|
VideoReceiverInfo rinfo;
|
|
rinfo.add_ssrc(ssrc);
|
|
rinfo.bytes_rcvd = received.transmitted.payload_bytes;
|
|
rinfo.packets_rcvd = received.transmitted.packets;
|
|
rinfo.packets_lost = -1;
|
|
rinfo.packets_concealed = -1;
|
|
rinfo.fraction_lost = -1; // from SentRTCP
|
|
rinfo.frame_width = channel->render_adapter()->width();
|
|
rinfo.frame_height = channel->render_adapter()->height();
|
|
int fps = channel->render_adapter()->framerate();
|
|
rinfo.framerate_decoded = fps;
|
|
rinfo.framerate_output = fps;
|
|
rinfo.capture_start_ntp_time_ms =
|
|
channel->render_adapter()->capture_start_ntp_time_ms();
|
|
channel->decoder_observer()->ExportTo(&rinfo);
|
|
|
|
webrtc::RtcpPacketTypeCounter rtcp_sent;
|
|
webrtc::RtcpPacketTypeCounter rtcp_received;
|
|
if (engine()->vie()->rtp()->GetRtcpPacketTypeCounters(
|
|
channel->channel_id(), &rtcp_sent, &rtcp_received) == 0) {
|
|
rinfo.firs_sent = rtcp_sent.fir_packets;
|
|
rinfo.plis_sent = rtcp_sent.pli_packets;
|
|
rinfo.nacks_sent = rtcp_sent.nack_packets;
|
|
} else {
|
|
rinfo.firs_sent = -1;
|
|
rinfo.plis_sent = -1;
|
|
rinfo.nacks_sent = -1;
|
|
LOG_RTCERR1(GetRtcpPacketTypeCounters, channel->channel_id());
|
|
}
|
|
|
|
// Get our locally created statistics of the received RTP stream.
|
|
webrtc::RtcpStatistics incoming_stream_rtcp_stats;
|
|
int64_t incoming_stream_rtt_ms;
|
|
if (engine_->vie()->rtp()->GetReceiveChannelRtcpStatistics(
|
|
channel->channel_id(),
|
|
incoming_stream_rtcp_stats,
|
|
incoming_stream_rtt_ms) == 0) {
|
|
// Convert Q8 to float.
|
|
rinfo.packets_lost = incoming_stream_rtcp_stats.cumulative_lost;
|
|
rinfo.fraction_lost = static_cast<float>(
|
|
incoming_stream_rtcp_stats.fraction_lost) / (1 << 8);
|
|
}
|
|
info->receivers.push_back(rinfo);
|
|
}
|
|
unsigned int estimated_recv_bandwidth = 0;
|
|
if (!recv_channels_.empty()) {
|
|
// GetEstimatedReceiveBandwidth returns the estimated bandwidth for all
|
|
// video engine channels in a channel group. Any valid channel id will do as
|
|
// it is only used to access the right group of channels.
|
|
const int channel_id = recv_channels_.begin()->second->channel_id();
|
|
// Gets the estimated receive bandwidth for the MediaChannel.
|
|
if (engine_->vie()->rtp()->GetEstimatedReceiveBandwidth(
|
|
channel_id, &estimated_recv_bandwidth) != 0) {
|
|
LOG_RTCERR1(GetEstimatedReceiveBandwidth, channel_id);
|
|
}
|
|
}
|
|
|
|
// Build BandwidthEstimationInfo.
|
|
// TODO(zhurunz): Add real unittest for this.
|
|
BandwidthEstimationInfo bwe;
|
|
engine_->vie()->rtp()->GetPacerQueuingDelayMs(
|
|
GetDefaultRecvChannel()->channel_id(), &bwe.bucket_delay);
|
|
|
|
// Calculations done above per send/receive stream.
|
|
bwe.actual_enc_bitrate = video_bitrate_sent;
|
|
bwe.transmit_bitrate = total_bitrate_sent;
|
|
bwe.retransmit_bitrate = nack_bitrate_sent;
|
|
bwe.available_send_bandwidth = estimated_send_bandwidth;
|
|
bwe.available_recv_bandwidth = estimated_recv_bandwidth;
|
|
bwe.target_enc_bitrate = target_enc_bitrate;
|
|
|
|
info->bw_estimations.push_back(bwe);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetCapturer(uint32 ssrc,
|
|
VideoCapturer* capturer) {
|
|
ASSERT(ssrc != 0);
|
|
if (!capturer) {
|
|
return RemoveCapturer(ssrc);
|
|
}
|
|
WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrc(ssrc);
|
|
if (!send_channel) {
|
|
return false;
|
|
}
|
|
VideoCapturer* old_capturer = send_channel->video_capturer();
|
|
MaybeDisconnectCapturer(old_capturer);
|
|
|
|
send_channel->set_video_capturer(capturer, engine()->vie());
|
|
MaybeConnectCapturer(capturer);
|
|
if (!capturer->IsScreencast() && ratio_w_ != 0 && ratio_h_ != 0) {
|
|
capturer->UpdateAspectRatio(ratio_w_, ratio_h_);
|
|
}
|
|
const int64 timestamp = send_channel->local_stream_info()->time_stamp();
|
|
if (send_codec_) {
|
|
QueueBlackFrame(ssrc, timestamp,
|
|
VideoFormat::FpsToInterval(send_codec_->maxFramerate));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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(
|
|
rtc::Buffer* packet, const rtc::PacketTime& packet_time) {
|
|
// Pick which channel to send this packet to. If this packet doesn't match
|
|
// any multiplexed streams, just send it to the default channel. Otherwise,
|
|
// send it to the specific decoder instance for that stream.
|
|
uint32 ssrc = 0;
|
|
if (!GetRtpSsrc(packet->data(), packet->length(), &ssrc))
|
|
return;
|
|
int processing_channel_id = GetRecvChannelId(ssrc);
|
|
if (processing_channel_id == kChannelIdUnset) {
|
|
// Allocate an unsignalled recv channel for processing in conference mode.
|
|
if (!ConferenceModeIsEnabled()) {
|
|
// If we can't find or allocate one, use the default.
|
|
processing_channel_id = default_channel_id_;
|
|
} else if (!CreateUnsignalledRecvChannel(ssrc, &processing_channel_id)) {
|
|
// If we can't create an unsignalled recv channel, drop the packet in
|
|
// conference mode.
|
|
return;
|
|
}
|
|
}
|
|
|
|
engine()->vie()->network()->ReceivedRTPPacket(
|
|
processing_channel_id,
|
|
packet->data(),
|
|
packet->length(),
|
|
webrtc::PacketTime(packet_time.timestamp, packet_time.not_before));
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::OnRtcpReceived(
|
|
rtc::Buffer* packet, const rtc::PacketTime& packet_time) {
|
|
// Sending channels need all RTCP packets with feedback information.
|
|
// Even sender reports can contain attached report blocks.
|
|
// Receiving channels need sender reports in order to create
|
|
// correct receiver reports.
|
|
|
|
uint32 ssrc = 0;
|
|
if (!GetRtcpSsrc(packet->data(), packet->length(), &ssrc)) {
|
|
LOG(LS_WARNING) << "Failed to parse SSRC from received RTCP packet";
|
|
return;
|
|
}
|
|
int type = 0;
|
|
if (!GetRtcpType(packet->data(), packet->length(), &type)) {
|
|
LOG(LS_WARNING) << "Failed to parse type from received RTCP packet";
|
|
return;
|
|
}
|
|
|
|
// If it is a sender report, find the channel that is listening.
|
|
if (type == kRtcpTypeSR) {
|
|
int recv_channel_id = GetRecvChannelId(ssrc);
|
|
if (recv_channel_id != kChannelIdUnset && !IsDefaultChannelId(recv_channel_id)) {
|
|
engine_->vie()->network()->ReceivedRTCPPacket(
|
|
recv_channel_id,
|
|
packet->data(),
|
|
packet->length());
|
|
}
|
|
}
|
|
// SR may continue RR and any RR entry may correspond to any one of the send
|
|
// channels. So all RTCP packets must be forwarded all send channels. ViE
|
|
// will filter out RR internally.
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
int channel_id = send_channel->channel_id();
|
|
engine_->vie()->network()->ReceivedRTCPPacket(
|
|
channel_id,
|
|
packet->data(),
|
|
packet->length());
|
|
}
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::OnReadyToSend(bool ready) {
|
|
SetNetworkTransmissionState(ready);
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::MuteStream(uint32 ssrc, bool muted) {
|
|
WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrc(ssrc);
|
|
if (!send_channel) {
|
|
LOG(LS_ERROR) << "The specified ssrc " << ssrc << " is not in use.";
|
|
return false;
|
|
}
|
|
send_channel->set_muted(muted);
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetRecvRtpHeaderExtensions(
|
|
const std::vector<RtpHeaderExtension>& extensions) {
|
|
if (receive_extensions_ == extensions) {
|
|
return true;
|
|
}
|
|
|
|
const RtpHeaderExtension* offset_extension =
|
|
FindHeaderExtension(extensions, kRtpTimestampOffsetHeaderExtension);
|
|
const RtpHeaderExtension* send_time_extension =
|
|
FindHeaderExtension(extensions, kRtpAbsoluteSenderTimeHeaderExtension);
|
|
|
|
// Loop through all receive channels and enable/disable the extensions.
|
|
for (RecvChannelMap::iterator channel_it = recv_channels_.begin();
|
|
channel_it != recv_channels_.end(); ++channel_it) {
|
|
int channel_id = channel_it->second->channel_id();
|
|
if (!SetHeaderExtension(
|
|
&webrtc::ViERTP_RTCP::SetReceiveTimestampOffsetStatus, channel_id,
|
|
offset_extension)) {
|
|
return false;
|
|
}
|
|
if (!SetHeaderExtension(
|
|
&webrtc::ViERTP_RTCP::SetReceiveAbsoluteSendTimeStatus, channel_id,
|
|
send_time_extension)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
receive_extensions_ = extensions;
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetSendRtpHeaderExtensions(
|
|
const std::vector<RtpHeaderExtension>& extensions) {
|
|
if (send_extensions_ == extensions) {
|
|
return true;
|
|
}
|
|
|
|
const RtpHeaderExtension* offset_extension =
|
|
FindHeaderExtension(extensions, kRtpTimestampOffsetHeaderExtension);
|
|
const RtpHeaderExtension* send_time_extension =
|
|
FindHeaderExtension(extensions, kRtpAbsoluteSenderTimeHeaderExtension);
|
|
|
|
// Loop through all send channels and enable/disable the extensions.
|
|
for (SendChannelMap::iterator channel_it = send_channels_.begin();
|
|
channel_it != send_channels_.end(); ++channel_it) {
|
|
int channel_id = channel_it->second->channel_id();
|
|
if (!SetHeaderExtension(
|
|
&webrtc::ViERTP_RTCP::SetSendTimestampOffsetStatus, channel_id,
|
|
offset_extension)) {
|
|
return false;
|
|
}
|
|
if (!SetHeaderExtension(
|
|
&webrtc::ViERTP_RTCP::SetSendAbsoluteSendTimeStatus, channel_id,
|
|
send_time_extension)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (send_time_extension) {
|
|
// For video RTP packets, we would like to update AbsoluteSendTimeHeader
|
|
// Extension closer to the network, @ socket level before sending.
|
|
// Pushing the extension id to socket layer.
|
|
MediaChannel::SetOption(NetworkInterface::ST_RTP,
|
|
rtc::Socket::OPT_RTP_SENDTIME_EXTN_ID,
|
|
send_time_extension->id);
|
|
}
|
|
|
|
send_extensions_ = extensions;
|
|
return true;
|
|
}
|
|
|
|
int WebRtcVideoMediaChannel::GetRtpSendTimeExtnId() const {
|
|
const RtpHeaderExtension* send_time_extension = FindHeaderExtension(
|
|
send_extensions_, kRtpAbsoluteSenderTimeHeaderExtension);
|
|
if (send_time_extension) {
|
|
return send_time_extension->id;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetMaxSendBandwidth(int bps) {
|
|
LOG(LS_INFO) << "WebRtcVideoMediaChannel::SetMaxSendBandwidth";
|
|
|
|
if (!send_codec_) {
|
|
LOG(LS_INFO) << "The send codec has not been set up yet";
|
|
return true;
|
|
}
|
|
|
|
webrtc::VideoCodec new_codec = *send_codec_;
|
|
if (BitrateIsSet(bps)) {
|
|
new_codec.maxBitrate = bps / 1000;
|
|
}
|
|
if (!SetSendCodec(new_codec)) {
|
|
return false;
|
|
}
|
|
LogSendCodecChange("SetMaxSendBandwidth()");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) {
|
|
// Always accept options that are unchanged.
|
|
if (options_ == options) {
|
|
return true;
|
|
}
|
|
|
|
// Save the options, to be interpreted where appropriate.
|
|
// Use options_.SetAll() instead of assignment so that unset value in options
|
|
// will not overwrite the previous option value.
|
|
VideoOptions original = options_;
|
|
options_.SetAll(options);
|
|
|
|
Clamp(&options_.system_low_adaptation_threshhold, 0.0f, 1.0f);
|
|
Clamp(&options_.system_high_adaptation_threshhold, 0.0f, 1.0f);
|
|
|
|
bool use_simulcast_adapter;
|
|
if (options.use_simulcast_adapter.Get(&use_simulcast_adapter) &&
|
|
options.use_simulcast_adapter != original.use_simulcast_adapter) {
|
|
webrtc::VP8EncoderFactoryConfig::set_use_simulcast_adapter(
|
|
use_simulcast_adapter);
|
|
}
|
|
|
|
// Set CPU options and codec options for all send channels.
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
send_channel->ApplyCpuOptions(options_);
|
|
|
|
if (send_codec_) {
|
|
VideoSendParams send_params = send_channel->send_params();
|
|
|
|
bool conference_mode_turned_off = (
|
|
original.conference_mode.IsSet() &&
|
|
options.conference_mode.IsSet() &&
|
|
original.conference_mode.GetWithDefaultIfUnset(false) &&
|
|
!options.conference_mode.GetWithDefaultIfUnset(false));
|
|
if (conference_mode_turned_off) {
|
|
// This is a special case for turning conference mode off.
|
|
// Max bitrate should go back to the default maximum value instead
|
|
// of the current maximum.
|
|
send_params.codec.maxBitrate = kAutoBandwidth;
|
|
}
|
|
|
|
// TODO(pthatcher): Remove this. We don't need 4 ways to set bitrates.
|
|
int new_start_bitrate;
|
|
if (options.video_start_bitrate.Get(&new_start_bitrate)) {
|
|
send_params.codec.startBitrate = new_start_bitrate;
|
|
}
|
|
|
|
if (!SetSendParams(send_channel, send_params)) {
|
|
return false;
|
|
}
|
|
LogSendCodecChange("SetOptions()");
|
|
}
|
|
}
|
|
|
|
|
|
int buffer_latency;
|
|
if (Changed(options.buffered_mode_latency,
|
|
original.buffered_mode_latency,
|
|
&buffer_latency)) {
|
|
LOG(LS_INFO) << "Buffer latency is " << buffer_latency;
|
|
for (SendChannelMap::iterator it = send_channels_.begin();
|
|
it != send_channels_.end(); ++it) {
|
|
if (engine()->vie()->rtp()->SetSenderBufferingMode(
|
|
it->second->channel_id(), buffer_latency) != 0) {
|
|
LOG_RTCERR2(SetSenderBufferingMode, it->second->channel_id(),
|
|
buffer_latency);
|
|
}
|
|
}
|
|
for (RecvChannelMap::iterator it = recv_channels_.begin();
|
|
it != recv_channels_.end(); ++it) {
|
|
if (engine()->vie()->rtp()->SetReceiverBufferingMode(
|
|
it->second->channel_id(), buffer_latency) != 0) {
|
|
LOG_RTCERR2(SetReceiverBufferingMode, it->second->channel_id(),
|
|
buffer_latency);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool dscp_enabled;
|
|
if (Changed(options.dscp, original.dscp, &dscp_enabled)) {
|
|
rtc::DiffServCodePoint dscp = rtc::DSCP_DEFAULT;
|
|
if (dscp_enabled) {
|
|
dscp = kVideoDscpValue;
|
|
}
|
|
LOG(LS_INFO) << "DSCP is " << dscp;
|
|
if (MediaChannel::SetDscp(dscp) != 0) {
|
|
LOG(LS_WARNING) << "Failed to set DSCP settings for video channel";
|
|
}
|
|
}
|
|
|
|
bool suspend_below_min_bitrate;
|
|
if (Changed(options.suspend_below_min_bitrate,
|
|
original.suspend_below_min_bitrate,
|
|
&suspend_below_min_bitrate)) {
|
|
if (suspend_below_min_bitrate) {
|
|
LOG(LS_INFO) << "Suspend below min bitrate enabled.";
|
|
for (SendChannelMap::iterator it = send_channels_.begin();
|
|
it != send_channels_.end(); ++it) {
|
|
engine()->vie()->codec()->SuspendBelowMinBitrate(
|
|
it->second->channel_id());
|
|
}
|
|
} else {
|
|
LOG(LS_WARNING) << "Cannot disable video suspension once it is enabled";
|
|
}
|
|
}
|
|
|
|
webrtc::CpuOveruseOptions overuse_options;
|
|
if (GetCpuOveruseOptions(options_, &overuse_options)) {
|
|
for (SendChannelMap::iterator it = send_channels_.begin();
|
|
it != send_channels_.end(); ++it) {
|
|
if (engine()->vie()->base()->SetCpuOveruseOptions(
|
|
it->second->channel_id(), overuse_options) != 0) {
|
|
LOG_RTCERR1(SetCpuOveruseOptions, it->second->channel_id());
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::SetInterface(NetworkInterface* iface) {
|
|
MediaChannel::SetInterface(iface);
|
|
// Set the RTP recv/send buffer to a bigger size
|
|
MediaChannel::SetOption(NetworkInterface::ST_RTP,
|
|
rtc::Socket::OPT_RCVBUF,
|
|
kVideoRtpBufferSize);
|
|
|
|
// Speculative change to increase the outbound socket buffer size.
|
|
// In b/15152257, we are seeing a significant number of packets discarded
|
|
// due to lack of socket buffer space, although it's not yet clear what the
|
|
// ideal value should be.
|
|
MediaChannel::SetOption(NetworkInterface::ST_RTP,
|
|
rtc::Socket::OPT_SNDBUF,
|
|
kVideoRtpBufferSize);
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::UpdateAspectRatio(int ratio_w, int ratio_h) {
|
|
ASSERT(ratio_w != 0);
|
|
ASSERT(ratio_h != 0);
|
|
ratio_w_ = ratio_w;
|
|
ratio_h_ = ratio_h;
|
|
// For now assume that all streams want the same aspect ratio.
|
|
// TODO(hellner): remove the need for this assumption.
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
VideoCapturer* capturer = send_channel->video_capturer();
|
|
if (capturer) {
|
|
capturer->UpdateAspectRatio(ratio_w, ratio_h);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::GetRenderer(uint32 ssrc,
|
|
VideoRenderer** renderer) {
|
|
WebRtcVideoChannelRecvInfo* recv_channel = GetRecvChannelBySsrc(ssrc);
|
|
if (!recv_channel) {
|
|
if (first_receive_ssrc_ == ssrc && GetDefaultRecvChannel()) {
|
|
LOG(LS_INFO) << " GetRenderer " << ssrc
|
|
<< " reuse default renderer #"
|
|
<< default_channel_id_;
|
|
*renderer = GetDefaultRecvChannel()->render_adapter()->renderer();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
*renderer = recv_channel->render_adapter()->renderer();
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::GetVideoAdapter(
|
|
uint32 ssrc, CoordinatedVideoAdapter** video_adapter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrc(ssrc);
|
|
if (!send_channel) {
|
|
return false;
|
|
}
|
|
*video_adapter = send_channel->video_adapter();
|
|
return true;
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::SendFrame(VideoCapturer* capturer,
|
|
const VideoFrame* frame) {
|
|
// If the |capturer| is registered to any send channel, then send the frame
|
|
// to those send channels.
|
|
bool capturer_is_channel_owned = false;
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
if (send_channel->video_capturer() == capturer) {
|
|
SendFrame(send_channel, frame, capturer->IsScreencast());
|
|
capturer_is_channel_owned = true;
|
|
}
|
|
}
|
|
if (capturer_is_channel_owned) {
|
|
return;
|
|
}
|
|
|
|
// TODO(hellner): Remove below for loop once the captured frame no longer
|
|
// come from the engine, i.e. the engine no longer owns a capturer.
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
if (!send_channel->video_capturer()) {
|
|
SendFrame(send_channel, frame, capturer->IsScreencast());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SendFrame(
|
|
WebRtcVideoChannelSendInfo* send_channel,
|
|
const VideoFrame* frame,
|
|
bool is_screencast) {
|
|
if (!send_channel) {
|
|
return false;
|
|
}
|
|
|
|
bool changed;
|
|
send_channel->SetLastCapturedFrameInfo(frame, is_screencast, &changed);
|
|
|
|
if (!send_codec_) {
|
|
// Send codec has not been set. No reason to process the frame any further.
|
|
return false;
|
|
}
|
|
|
|
// TODO(pthatcher): Move drop logic to adapter.
|
|
// If the frame should be dropped.
|
|
if (send_channel->adapt_format_set() &&
|
|
send_channel->adapt_format().width == 0 &&
|
|
send_channel->adapt_format().height == 0) {
|
|
return true;
|
|
}
|
|
|
|
if (changed) {
|
|
// If the last captured frame info changed, then calling
|
|
// SetSendParams will update to the latest resolution.
|
|
VideoSendParams send_params = send_channel->send_params();
|
|
// Note: We must copy the send_params because otherwise the memory
|
|
// checker will complain.
|
|
if (!SetSendParams(send_channel, send_params)) {
|
|
LOG(LS_ERROR) << "SetSendParams from SendFrame failed with "
|
|
<< frame->GetWidth() << "x" << frame->GetHeight()
|
|
<< " screencast? " << is_screencast;
|
|
return false;
|
|
}
|
|
LogSendCodecChange("Captured frame size changed");
|
|
}
|
|
|
|
const VideoFrame* frame_out = frame;
|
|
rtc::scoped_ptr<VideoFrame> processed_frame;
|
|
// TODO(hellner): Remove the need for disabling mute when screencasting.
|
|
const bool mute = (send_channel->muted() && !is_screencast);
|
|
send_channel->ProcessFrame(*frame_out, mute, processed_frame.use());
|
|
if (processed_frame) {
|
|
frame_out = processed_frame.get();
|
|
}
|
|
|
|
webrtc::I420VideoFrame webrtc_frame(frame_out->GetVideoFrameBuffer(), 0, 0,
|
|
frame_out->GetVideoRotation());
|
|
int64 timestamp_ntp_ms = 0;
|
|
// TODO(justinlin): Reenable after Windows issues with clock drift are fixed.
|
|
// Currently reverted to old behavior of discarding capture timestamp.
|
|
#if 0
|
|
static const int kTimestampDeltaInSecondsForWarning = 2;
|
|
|
|
// If the frame timestamp is 0, we will use the deliver time.
|
|
const int64 frame_timestamp = frame->GetTimeStamp();
|
|
if (frame_timestamp != 0) {
|
|
if (abs(time(NULL) - frame_timestamp / rtc::kNumNanosecsPerSec) >
|
|
kTimestampDeltaInSecondsForWarning) {
|
|
LOG(LS_WARNING) << "Frame timestamp differs by more than "
|
|
<< kTimestampDeltaInSecondsForWarning << " seconds from "
|
|
<< "current Unix timestamp.";
|
|
}
|
|
|
|
timestamp_ntp_ms =
|
|
rtc::UnixTimestampNanosecsToNtpMillisecs(frame_timestamp);
|
|
}
|
|
#endif
|
|
webrtc_frame.set_ntp_time_ms(timestamp_ntp_ms);
|
|
send_channel->external_capture()->IncomingFrame(webrtc_frame);
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::CreateChannel(uint32 ssrc_key,
|
|
MediaDirection direction,
|
|
int* channel_id) {
|
|
// There are 3 types of channels. Sending only, receiving only and
|
|
// sending and receiving. The sending and receiving channel is the
|
|
// default channel and there is only one. All other channels that
|
|
// are created are associated with the default channel which must
|
|
// exist. The default channel id is stored in
|
|
// |default_channel_id_|. All channels need to know about the
|
|
// default channel to properly handle remb which is why there are
|
|
// different ViE create channel calls. For this channel the local
|
|
// and remote ssrc_key is kDefaultChannelSsrcKey. However, it may
|
|
// have a non-zero local and/or remote ssrc depending on if it is
|
|
// currently sending and/or receiving.
|
|
if ((default_channel_id_ == kChannelIdUnset || direction == MD_SENDRECV) &&
|
|
(!send_channels_.empty() || !recv_channels_.empty())) {
|
|
ASSERT(false);
|
|
return false;
|
|
}
|
|
|
|
*channel_id = kChannelIdUnset;
|
|
if (direction == MD_RECV) {
|
|
// All rec channels are associated with default_channel_id_.
|
|
if (engine_->vie()->base()->CreateReceiveChannel(*channel_id,
|
|
default_channel_id_) != 0) {
|
|
LOG_RTCERR2(CreateReceiveChannel, *channel_id, default_channel_id_);
|
|
return false;
|
|
}
|
|
} else if (direction == MD_SEND) {
|
|
if (engine_->vie()->base()->CreateChannel(*channel_id,
|
|
default_channel_id_) != 0) {
|
|
LOG_RTCERR2(CreateChannel, *channel_id, default_channel_id_);
|
|
return false;
|
|
}
|
|
} else {
|
|
ASSERT(direction == MD_SENDRECV);
|
|
if (engine_->vie()->base()->CreateChannel(*channel_id) != 0) {
|
|
LOG_RTCERR1(CreateChannel, *channel_id);
|
|
return false;
|
|
}
|
|
}
|
|
if (!ConfigureChannel(*channel_id, direction, ssrc_key)) {
|
|
engine_->vie()->base()->DeleteChannel(*channel_id);
|
|
*channel_id = kChannelIdUnset;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::CreateUnsignalledRecvChannel(
|
|
uint32 ssrc_key, int* out_channel_id) {
|
|
int unsignalled_recv_channel_limit =
|
|
options_.unsignalled_recv_stream_limit.GetWithDefaultIfUnset(
|
|
kNumDefaultUnsignalledVideoRecvStreams);
|
|
if (num_unsignalled_recv_channels_ >= unsignalled_recv_channel_limit) {
|
|
return false;
|
|
}
|
|
if (!CreateChannel(ssrc_key, MD_RECV, out_channel_id)) {
|
|
return false;
|
|
}
|
|
// TODO(tvsriram): Support dynamic sizing of unsignalled recv channels.
|
|
num_unsignalled_recv_channels_++;
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::ConfigureChannel(int channel_id,
|
|
MediaDirection direction,
|
|
uint32 ssrc_key) {
|
|
const bool receiving = (direction == MD_RECV) || (direction == MD_SENDRECV);
|
|
const bool sending = (direction == MD_SEND) || (direction == MD_SENDRECV);
|
|
// Register external transport.
|
|
if (engine_->vie()->network()->RegisterSendTransport(
|
|
channel_id, *this) != 0) {
|
|
LOG_RTCERR1(RegisterSendTransport, channel_id);
|
|
return false;
|
|
}
|
|
|
|
// Set MTU.
|
|
if (engine_->vie()->network()->SetMTU(channel_id, kVideoMtu) != 0) {
|
|
LOG_RTCERR2(SetMTU, channel_id, kVideoMtu);
|
|
return false;
|
|
}
|
|
// Turn on RTCP and loss feedback reporting.
|
|
if (engine()->vie()->rtp()->SetRTCPStatus(
|
|
channel_id, webrtc::kRtcpCompound_RFC4585) != 0) {
|
|
LOG_RTCERR2(SetRTCPStatus, channel_id, webrtc::kRtcpCompound_RFC4585);
|
|
return false;
|
|
}
|
|
// Enable pli as key frame request method.
|
|
if (engine_->vie()->rtp()->SetKeyFrameRequestMethod(
|
|
channel_id, webrtc::kViEKeyFrameRequestPliRtcp) != 0) {
|
|
LOG_RTCERR2(SetKeyFrameRequestMethod,
|
|
channel_id, webrtc::kViEKeyFrameRequestPliRtcp);
|
|
return false;
|
|
}
|
|
if (!SetNackFec(channel_id, send_red_type_, send_fec_type_, nack_enabled_)) {
|
|
// Logged in SetNackFec. Don't spam the logs.
|
|
return false;
|
|
}
|
|
// Note that receiving must always be configured before sending to ensure
|
|
// that send and receive channel is configured correctly (ConfigureReceiving
|
|
// assumes no sending).
|
|
if (receiving) {
|
|
if (!ConfigureReceiving(channel_id, ssrc_key)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (sending) {
|
|
if (!ConfigureSending(channel_id, ssrc_key)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Start receiving for both receive and send channels so that we get incoming
|
|
// RTP (if receiving) as well as RTCP feedback (if sending).
|
|
if (engine()->vie()->base()->StartReceive(channel_id) != 0) {
|
|
LOG_RTCERR1(StartReceive, channel_id);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::ConfigureReceiving(int channel_id,
|
|
uint32 remote_ssrc) {
|
|
// Make sure that an SSRC isn't registered more than once.
|
|
if (GetRecvChannelBySsrc(remote_ssrc)) {
|
|
return false;
|
|
}
|
|
// Connect the voice channel, if there is one.
|
|
// TODO(perkj): The A/V is synched by the receiving channel. So we need to
|
|
// know the SSRC of the remote audio channel in order to fetch the correct
|
|
// webrtc VoiceEngine channel. For now- only sync the default channel used
|
|
// in 1-1 calls.
|
|
if (remote_ssrc == kDefaultChannelSsrcKey && voice_channel_) {
|
|
WebRtcVoiceMediaChannel* voice_channel =
|
|
static_cast<WebRtcVoiceMediaChannel*>(voice_channel_);
|
|
if (engine_->vie()->base()->ConnectAudioChannel(
|
|
default_channel_id_, voice_channel->voe_channel()) != 0) {
|
|
LOG_RTCERR2(ConnectAudioChannel, channel_id,
|
|
voice_channel->voe_channel());
|
|
LOG(LS_WARNING) << "A/V not synchronized";
|
|
// Not a fatal error.
|
|
}
|
|
}
|
|
|
|
rtc::scoped_ptr<WebRtcVideoChannelRecvInfo> channel_info(
|
|
new WebRtcVideoChannelRecvInfo(channel_id));
|
|
|
|
// Install a render adapter.
|
|
if (engine_->vie()->render()->AddRenderer(channel_id,
|
|
webrtc::kVideoI420, channel_info->render_adapter()) != 0) {
|
|
LOG_RTCERR3(AddRenderer, channel_id, webrtc::kVideoI420,
|
|
channel_info->render_adapter());
|
|
return false;
|
|
}
|
|
|
|
if (engine()->vie()->render()->SetExpectedRenderDelay(
|
|
channel_id, kDefaultRenderDelayMs)) {
|
|
LOG_RTCERR2(SetExpectedRenderDelay,
|
|
channel_id, kDefaultRenderDelayMs);
|
|
}
|
|
|
|
if (engine_->vie()->rtp()->SetRembStatus(channel_id,
|
|
kNotSending,
|
|
remb_enabled_) != 0) {
|
|
LOG_RTCERR3(SetRembStatus, channel_id, kNotSending, remb_enabled_);
|
|
return false;
|
|
}
|
|
|
|
if (!SetHeaderExtension(&webrtc::ViERTP_RTCP::SetReceiveTimestampOffsetStatus,
|
|
channel_id, receive_extensions_, kRtpTimestampOffsetHeaderExtension)) {
|
|
return false;
|
|
}
|
|
if (!SetHeaderExtension(
|
|
&webrtc::ViERTP_RTCP::SetReceiveAbsoluteSendTimeStatus, channel_id,
|
|
receive_extensions_, kRtpAbsoluteSenderTimeHeaderExtension)) {
|
|
return false;
|
|
}
|
|
|
|
if (receiver_report_ssrc_ != kSsrcUnset) {
|
|
if (engine()->vie()->rtp()->SetLocalSSRC(
|
|
channel_id, receiver_report_ssrc_) == -1) {
|
|
LOG_RTCERR2(SetLocalSSRC, channel_id, receiver_report_ssrc_);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Disable color enhancement since it is a bit too aggressive.
|
|
if (engine()->vie()->image()->EnableColorEnhancement(channel_id,
|
|
false) != 0) {
|
|
LOG_RTCERR1(EnableColorEnhancement, channel_id);
|
|
return false;
|
|
}
|
|
|
|
if (!SetReceiveCodecs(channel_info.get())) {
|
|
return false;
|
|
}
|
|
|
|
int buffer_latency =
|
|
options_.buffered_mode_latency.GetWithDefaultIfUnset(
|
|
cricket::kBufferedModeDisabled);
|
|
if (buffer_latency != cricket::kBufferedModeDisabled) {
|
|
if (engine()->vie()->rtp()->SetReceiverBufferingMode(
|
|
channel_id, buffer_latency) != 0) {
|
|
LOG_RTCERR2(SetReceiverBufferingMode, channel_id, buffer_latency);
|
|
}
|
|
}
|
|
|
|
if (render_started_) {
|
|
if (engine_->vie()->render()->StartRender(channel_id) != 0) {
|
|
LOG_RTCERR1(StartRender, channel_id);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Register decoder observer for incoming framerate and bitrate.
|
|
if (engine()->vie()->codec()->RegisterDecoderObserver(
|
|
channel_id, *channel_info->decoder_observer()) != 0) {
|
|
LOG_RTCERR1(RegisterDecoderObserver, channel_info->decoder_observer());
|
|
return false;
|
|
}
|
|
|
|
recv_channels_[remote_ssrc] = channel_info.release();
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::ConfigureSending(int channel_id,
|
|
uint32 local_ssrc_key) {
|
|
// The ssrc key can be zero or correspond to an SSRC.
|
|
// Make sure the default channel isn't configured more than once.
|
|
if (local_ssrc_key == kDefaultChannelSsrcKey && GetDefaultSendChannel()) {
|
|
return false;
|
|
}
|
|
// Make sure that the SSRC is not already in use.
|
|
uint32 dummy_key;
|
|
if (GetSendChannelSsrcKey(local_ssrc_key, &dummy_key)) {
|
|
return false;
|
|
}
|
|
int vie_capture = 0;
|
|
webrtc::ViEExternalCapture* external_capture = NULL;
|
|
// 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, channel_id) != 0) {
|
|
LOG_RTCERR2(ConnectCaptureDevice, vie_capture, channel_id);
|
|
return false;
|
|
}
|
|
|
|
// Set up a new send channel.
|
|
rtc::scoped_ptr<WebRtcVideoChannelSendInfo> send_channel(
|
|
new WebRtcVideoChannelSendInfo(channel_id, vie_capture,
|
|
external_capture,
|
|
engine()->cpu_monitor()));
|
|
send_channel->ApplyCpuOptions(options_);
|
|
send_channel->SignalCpuAdaptationUnable.connect(this,
|
|
&WebRtcVideoMediaChannel::OnCpuAdaptationUnable);
|
|
|
|
webrtc::CpuOveruseOptions overuse_options;
|
|
if (GetCpuOveruseOptions(options_, &overuse_options)) {
|
|
if (engine()->vie()->base()->SetCpuOveruseOptions(channel_id,
|
|
overuse_options) != 0) {
|
|
LOG_RTCERR1(SetCpuOveruseOptions, channel_id);
|
|
}
|
|
}
|
|
|
|
// Register encoder observer for outgoing framerate and bitrate.
|
|
if (engine()->vie()->codec()->RegisterEncoderObserver(
|
|
channel_id, *send_channel->encoder_observer()) != 0) {
|
|
LOG_RTCERR1(RegisterEncoderObserver, send_channel->encoder_observer());
|
|
return false;
|
|
}
|
|
|
|
if (!SetHeaderExtension(&webrtc::ViERTP_RTCP::SetSendTimestampOffsetStatus,
|
|
channel_id, send_extensions_, kRtpTimestampOffsetHeaderExtension)) {
|
|
return false;
|
|
}
|
|
|
|
if (!SetHeaderExtension(&webrtc::ViERTP_RTCP::SetSendAbsoluteSendTimeStatus,
|
|
channel_id, send_extensions_, kRtpAbsoluteSenderTimeHeaderExtension)) {
|
|
return false;
|
|
}
|
|
|
|
if (engine()->vie()->rtp()->SetTransmissionSmoothingStatus(channel_id,
|
|
true) != 0) {
|
|
LOG_RTCERR2(SetTransmissionSmoothingStatus, channel_id, true);
|
|
return false;
|
|
}
|
|
|
|
int buffer_latency =
|
|
options_.buffered_mode_latency.GetWithDefaultIfUnset(
|
|
cricket::kBufferedModeDisabled);
|
|
if (buffer_latency != cricket::kBufferedModeDisabled) {
|
|
if (engine()->vie()->rtp()->SetSenderBufferingMode(
|
|
channel_id, buffer_latency) != 0) {
|
|
LOG_RTCERR2(SetSenderBufferingMode, channel_id, buffer_latency);
|
|
}
|
|
}
|
|
|
|
if (options_.suspend_below_min_bitrate.GetWithDefaultIfUnset(false)) {
|
|
engine()->vie()->codec()->SuspendBelowMinBitrate(channel_id);
|
|
}
|
|
|
|
// The remb status direction correspond to the RTP stream (and not the RTCP
|
|
// stream). I.e. if send remb is enabled it means it is receiving remote
|
|
// rembs and should use them to estimate bandwidth. Receive remb mean that
|
|
// remb packets will be generated and that the channel should be included in
|
|
// it. If remb is enabled all channels are allowed to contribute to the remb
|
|
// but only receive channels will ever end up actually contributing. This
|
|
// keeps the logic simple.
|
|
if (engine_->vie()->rtp()->SetRembStatus(channel_id,
|
|
remb_enabled_,
|
|
remb_enabled_) != 0) {
|
|
LOG_RTCERR3(SetRembStatus, channel_id, remb_enabled_, remb_enabled_);
|
|
return false;
|
|
}
|
|
if (!SetNackFec(channel_id, send_red_type_, send_fec_type_, nack_enabled_)) {
|
|
// Logged in SetNackFec. Don't spam the logs.
|
|
return false;
|
|
}
|
|
|
|
send_channels_[local_ssrc_key] = send_channel.release();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetNackFec(int channel_id,
|
|
int red_payload_type,
|
|
int fec_payload_type,
|
|
bool nack_enabled) {
|
|
bool enable = (red_payload_type != -1 && fec_payload_type != -1 &&
|
|
!ConferenceModeIsEnabled());
|
|
if (enable) {
|
|
if (engine_->vie()->rtp()->SetHybridNACKFECStatus(
|
|
channel_id, nack_enabled, red_payload_type, fec_payload_type) != 0) {
|
|
LOG_RTCERR4(SetHybridNACKFECStatus, channel_id, nack_enabled,
|
|
red_payload_type, fec_payload_type);
|
|
return false;
|
|
}
|
|
LOG(LS_INFO) << "Hybrid NACK/FEC enabled for channel " << channel_id;
|
|
} else {
|
|
if (engine_->vie()->rtp()->SetNACKStatus(channel_id, nack_enabled) != 0) {
|
|
LOG_RTCERR1(SetNACKStatus, channel_id);
|
|
return false;
|
|
}
|
|
std::string enabled = nack_enabled ? "enabled" : "disabled";
|
|
LOG(LS_INFO) << "NACK " << enabled << " for channel " << channel_id;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetSendCodec(const webrtc::VideoCodec& codec) {
|
|
bool ret_val = true;
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
ret_val = SetSendCodec(send_channel, codec) && ret_val;
|
|
}
|
|
if (ret_val) {
|
|
// All SetSendCodec calls were successful. Update the global state
|
|
// accordingly.
|
|
send_codec_.reset(new webrtc::VideoCodec(codec));
|
|
} else {
|
|
// At least one SetSendCodec call failed, rollback.
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
if (send_codec_) {
|
|
SetSendCodec(send_channel, *send_codec_);
|
|
}
|
|
}
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetSendCodec(
|
|
WebRtcVideoChannelSendInfo* send_channel,
|
|
const webrtc::VideoCodec& codec) {
|
|
if (!send_channel) {
|
|
return false;
|
|
}
|
|
|
|
send_channel->SetAdaptFormat(
|
|
VideoFormatFromVieCodec(codec),
|
|
WebRtcVideoChannelSendInfo::kAdaptFormatTypeCodec);
|
|
|
|
VideoSendParams send_params = send_channel->send_params();
|
|
send_params.codec = codec;
|
|
return SetSendParams(send_channel, send_params);
|
|
}
|
|
|
|
static std::string ToString(webrtc::VideoCodecComplexity complexity) {
|
|
switch (complexity) {
|
|
case webrtc::kComplexityNormal:
|
|
return "normal";
|
|
case webrtc::kComplexityHigh:
|
|
return "high";
|
|
case webrtc::kComplexityHigher:
|
|
return "higher";
|
|
case webrtc::kComplexityMax:
|
|
return "max";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static std::string ToString(webrtc::VP8ResilienceMode resilience) {
|
|
switch (resilience) {
|
|
case webrtc::kResilienceOff:
|
|
return "off";
|
|
case webrtc::kResilientStream:
|
|
return "stream";
|
|
case webrtc::kResilientFrames:
|
|
return "frames";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::LogSendCodecChange(const std::string& reason) {
|
|
webrtc::VideoCodec vie_codec;
|
|
if (engine()->vie()->codec()->GetSendCodec(default_channel_id_, vie_codec) != 0) {
|
|
LOG_RTCERR1(GetSendCodec, default_channel_id_);
|
|
return;
|
|
}
|
|
|
|
LOG(LS_INFO) << reason << " : selected video codec "
|
|
<< vie_codec.plName << "/"
|
|
<< vie_codec.width << "x" << vie_codec.height << "x"
|
|
<< static_cast<int>(vie_codec.maxFramerate) << "fps"
|
|
<< "@" << vie_codec.maxBitrate << "kbps"
|
|
<< " (min=" << vie_codec.minBitrate << "kbps,"
|
|
<< " start=" << vie_codec.startBitrate << "kbps)";
|
|
LOG(LS_INFO) << "Video max quantization: " << vie_codec.qpMax;
|
|
if (webrtc::kVideoCodecVP8 == vie_codec.codecType) {
|
|
LOG(LS_INFO) << "VP8 number of temporal layers: "
|
|
<< static_cast<int>(
|
|
vie_codec.codecSpecific.VP8.numberOfTemporalLayers);
|
|
LOG(LS_INFO) << "VP8 options : "
|
|
<< "picture loss indication = "
|
|
<< vie_codec.codecSpecific.VP8.pictureLossIndicationOn
|
|
<< ", feedback mode = "
|
|
<< vie_codec.codecSpecific.VP8.feedbackModeOn
|
|
<< ", complexity = "
|
|
<< ToString(vie_codec.codecSpecific.VP8.complexity)
|
|
<< ", resilience = "
|
|
<< ToString(vie_codec.codecSpecific.VP8.resilience)
|
|
<< ", denoising = "
|
|
<< vie_codec.codecSpecific.VP8.denoisingOn
|
|
<< ", error concealment = "
|
|
<< vie_codec.codecSpecific.VP8.errorConcealmentOn
|
|
<< ", automatic resize = "
|
|
<< vie_codec.codecSpecific.VP8.automaticResizeOn
|
|
<< ", frame dropping = "
|
|
<< vie_codec.codecSpecific.VP8.frameDroppingOn
|
|
<< ", key frame interval = "
|
|
<< vie_codec.codecSpecific.VP8.keyFrameInterval;
|
|
}
|
|
|
|
if (send_rtx_type_ != -1) {
|
|
LOG(LS_INFO) << "RTX payload type: " << send_rtx_type_;
|
|
}
|
|
|
|
LogSimulcastSubstreams(vie_codec);
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetReceiveCodecs(
|
|
WebRtcVideoChannelRecvInfo* info) {
|
|
int red_type = -1;
|
|
int fec_type = -1;
|
|
int channel_id = info->channel_id();
|
|
// Build a map from payload types to video codecs so that we easily can find
|
|
// out if associated payload types are referring to valid codecs.
|
|
std::map<int, webrtc::VideoCodec*> pt_to_codec;
|
|
for (std::vector<webrtc::VideoCodec>::iterator it = receive_codecs_.begin();
|
|
it != receive_codecs_.end(); ++it) {
|
|
pt_to_codec[it->plType] = &(*it);
|
|
}
|
|
bool rtx_registered = false;
|
|
for (std::vector<webrtc::VideoCodec>::iterator it = receive_codecs_.begin();
|
|
it != receive_codecs_.end(); ++it) {
|
|
if (it->codecType == webrtc::kVideoCodecRED) {
|
|
red_type = it->plType;
|
|
} else if (it->codecType == webrtc::kVideoCodecULPFEC) {
|
|
fec_type = it->plType;
|
|
}
|
|
// If this is an RTX codec we have to verify that it is associated with
|
|
// a valid video codec which we have RTX support for.
|
|
if (_stricmp(it->plName, kRtxCodecName) == 0) {
|
|
// WebRTC only supports one RTX codec at a time.
|
|
if (rtx_registered) {
|
|
LOG(LS_ERROR) << "Only one RTX codec at a time is supported.";
|
|
return false;
|
|
}
|
|
std::map<int, int>::iterator apt_it =
|
|
associated_payload_types_.find(it->plType);
|
|
bool valid_apt = false;
|
|
if (apt_it != associated_payload_types_.end()) {
|
|
std::map<int, webrtc::VideoCodec*>::iterator codec_it =
|
|
pt_to_codec.find(apt_it->second);
|
|
valid_apt = codec_it != pt_to_codec.end();
|
|
}
|
|
if (!valid_apt) {
|
|
LOG(LS_ERROR) << "The RTX codec isn't associated with a known and "
|
|
"supported payload type";
|
|
return false;
|
|
}
|
|
if (engine()->vie()->rtp()->SetRtxReceivePayloadType(
|
|
channel_id, it->plType) != 0) {
|
|
LOG_RTCERR2(SetRtxReceivePayloadType, channel_id,
|
|
static_cast<int>(it->plType));
|
|
return false;
|
|
}
|
|
rtx_registered = true;
|
|
continue;
|
|
}
|
|
if (engine()->vie()->codec()->SetReceiveCodec(channel_id, *it) != 0) {
|
|
LOG_RTCERR2(SetReceiveCodec, channel_id, it->plName);
|
|
return false;
|
|
}
|
|
if (!info->IsDecoderRegistered(it->plType) &&
|
|
it->codecType != webrtc::kVideoCodecRED &&
|
|
it->codecType != webrtc::kVideoCodecULPFEC) {
|
|
webrtc::VideoDecoder* decoder =
|
|
engine()->CreateExternalDecoder(it->codecType);
|
|
if (decoder) {
|
|
if (engine()->vie()->ext_codec()->RegisterExternalReceiveCodec(
|
|
channel_id, it->plType, decoder) == 0) {
|
|
info->RegisterDecoder(it->plType, decoder);
|
|
} else {
|
|
LOG_RTCERR2(RegisterExternalReceiveCodec, channel_id, it->plName);
|
|
engine()->DestroyExternalDecoder(decoder);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int WebRtcVideoMediaChannel::GetRecvChannelId(uint32 ssrc) {
|
|
if (ssrc == first_receive_ssrc_) {
|
|
return default_channel_id_;
|
|
}
|
|
int recv_channel_id = kChannelIdUnset;
|
|
WebRtcVideoChannelRecvInfo* recv_channel = GetRecvChannelBySsrc(ssrc);
|
|
if (!recv_channel) {
|
|
// Check if we have an RTX stream registered on this SSRC.
|
|
SsrcMap::iterator rtx_it = rtx_to_primary_ssrc_.find(ssrc);
|
|
if (rtx_it != rtx_to_primary_ssrc_.end()) {
|
|
if (rtx_it->second == first_receive_ssrc_) {
|
|
recv_channel_id = default_channel_id_;
|
|
} else {
|
|
recv_channel = GetRecvChannelBySsrc(rtx_it->second);
|
|
ASSERT(recv_channel != NULL);
|
|
recv_channel_id = recv_channel->channel_id();
|
|
}
|
|
}
|
|
} else {
|
|
recv_channel_id = recv_channel->channel_id();
|
|
}
|
|
return recv_channel_id;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetSendParams(
|
|
WebRtcVideoChannelSendInfo* send_channel,
|
|
const VideoSendParams& send_params) {
|
|
ASSERT(engine()->worker_thread()->IsCurrent());
|
|
const int channel_id = send_channel->channel_id();
|
|
|
|
MaybeRegisterExternalEncoder(send_channel, send_params.codec);
|
|
|
|
CapturedFrameInfo frame;
|
|
if (!send_channel->last_captured_frame_info().Get(&frame)) {
|
|
// When we don't have a frame yet, configure the encoder with a
|
|
// QCIF (176x144) frame (to ensure HW encoder can be initialized).
|
|
// This gives us low memory usage but also makes it so configuration
|
|
// errors are discovered at the time we apply the settings rather than
|
|
// when we get the first frame (waiting for the first frame to know
|
|
// that you gave a bad codec parameter could make debugging hard).
|
|
frame.width = 176;
|
|
frame.height = 144;
|
|
|
|
// TODO(pthatcher): Evaluate the risk of not setting up an encoder
|
|
// at all until we have a frame. Once we feel it's worth the
|
|
// risk, we can do something like this:
|
|
// send_channel->set_send_params(send_params);
|
|
// return true;
|
|
}
|
|
|
|
// TODO(pthatcher): This checking of the max height and width is
|
|
// only needed because some unit tests bypass the VideoAdapter, and
|
|
// others expect behavior from the adapter different than what it
|
|
// actually does. We should fix the tests and remove this block.
|
|
VideoFormat max = send_channel->adapt_format();
|
|
size_t max_width = static_cast<size_t>(max.width);
|
|
size_t max_height = static_cast<size_t>(max.height);
|
|
if ((!frame.screencast &&
|
|
(frame.width > max_width || frame.height > max_height))) {
|
|
frame.width = max_width;
|
|
frame.height = max_height;
|
|
}
|
|
|
|
webrtc::VideoCodec codec;
|
|
ConfigureVieCodecFromSendParams(channel_id, send_params, frame, &codec);
|
|
// TODO(pthatcher): Figure out a clean way to configure the max
|
|
// framerate and sanitize the bitrates inside of
|
|
// ConfigureVieCodecFromSendParams.
|
|
codec.maxFramerate = max.framerate();
|
|
SanitizeBitrates(channel_id, &codec);
|
|
|
|
// Get current vie codec.
|
|
webrtc::VideoCodec current;
|
|
if (engine()->vie()->codec()->GetSendCodec(channel_id, current) != 0) {
|
|
LOG_RTCERR1(GetSendCodec, channel_id);
|
|
return false;
|
|
}
|
|
|
|
if (current != codec) {
|
|
if (engine()->vie()->codec()->SetSendCodec(channel_id, codec) != 0) {
|
|
LOG_RTCERR1(SetSendCodec, channel_id);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (frame.screencast) {
|
|
int screencast_min_bitrate =
|
|
options_.screencast_min_bitrate.GetWithDefaultIfUnset(0);
|
|
engine()->vie()->rtp()->SetMinTransmitBitrate(channel_id,
|
|
screencast_min_bitrate);
|
|
} else {
|
|
// In case of switching from screencast to regular capture, set
|
|
// min bitrate padding and pacer back to defaults.
|
|
engine()->vie()->rtp()->SetMinTransmitBitrate(channel_id, 0);
|
|
}
|
|
engine()->vie()->rtp()->SetTransmissionSmoothingStatus(channel_id, true);
|
|
|
|
// If the set of SSRCs isn't populated, then don't apply them. If we
|
|
// do, we'll cause a bug where adding a stream, then removing a
|
|
// stream, then re-adding a stream with the same primary SSRC will
|
|
// cause the sequence numbers to change and confuse the receiver due
|
|
// to jumping SRTP sequence numbers.
|
|
if (send_params.stream.first_ssrc() != 0) {
|
|
if (!SetSendSsrcs(channel_id, send_params.stream, codec)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// NOTE: SetRtxSendPayloadType must be called after all SSRCs are
|
|
// configured. Otherwise ssrc's configured after this point will use
|
|
// the primary PT for RTX.
|
|
if (send_rtx_type_ != -1 &&
|
|
engine()->vie()->rtp()->SetRtxSendPayloadType(channel_id,
|
|
send_rtx_type_) != 0) {
|
|
LOG_RTCERR2(SetRtxSendPayloadType, channel_id, send_rtx_type_);
|
|
return false;
|
|
}
|
|
|
|
send_channel->set_send_params(send_params);
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::ConfigureVieCodecFromSendParams(
|
|
int channel_id,
|
|
const VideoSendParams& send_params,
|
|
const CapturedFrameInfo& last_captured_frame_info,
|
|
webrtc::VideoCodec* codec_out) {
|
|
webrtc::VideoCodec codec = send_params.codec;
|
|
|
|
codec.width = static_cast<int>(last_captured_frame_info.width);
|
|
codec.height = static_cast<int>(last_captured_frame_info.height);
|
|
codec.targetBitrate = 0;
|
|
if (codec.codecType == webrtc::kVideoCodecVP8) {
|
|
codec.codecSpecific.VP8.numberOfTemporalLayers =
|
|
kDefaultNumberOfTemporalLayers;
|
|
codec.codecSpecific.VP8.resilience = webrtc::kResilienceOff;
|
|
}
|
|
|
|
if (last_captured_frame_info.screencast) {
|
|
codec.mode = webrtc::kScreensharing;
|
|
if (codec.codecType == webrtc::kVideoCodecVP8) {
|
|
codec.codecSpecific.VP8.denoisingOn = false;
|
|
codec.codecSpecific.VP8.automaticResizeOn = false;
|
|
codec.codecSpecific.VP8.frameDroppingOn = false;
|
|
}
|
|
} else {
|
|
codec.mode = webrtc::kRealtimeVideo;
|
|
if (codec.codecType == webrtc::kVideoCodecVP8) {
|
|
// TODO(pthatcher): Pass in options in VideoSendParams.
|
|
codec.codecSpecific.VP8.denoisingOn =
|
|
options_.video_noise_reduction.GetWithDefaultIfUnset(true);
|
|
codec.codecSpecific.VP8.automaticResizeOn = true;
|
|
codec.codecSpecific.VP8.frameDroppingOn = true;
|
|
}
|
|
}
|
|
|
|
if (webrtc::kVideoCodecVP8 == codec.codecType) {
|
|
ConfigureSimulcastTemporalLayers(
|
|
kDefaultNumberOfTemporalLayers, &codec);
|
|
if (IsSimulcastStream(send_params.stream)) {
|
|
codec.codecSpecific.VP8.automaticResizeOn = false;
|
|
// TODO(pthatcher): Pass in options in VideoSendParams.
|
|
VideoOptions options;
|
|
GetOptions(&options);
|
|
if (ConferenceModeIsEnabled()) {
|
|
ConfigureSimulcastCodec(send_params.stream, options, &codec);
|
|
}
|
|
}
|
|
|
|
if (last_captured_frame_info.screencast) {
|
|
// Use existing bitrate if not in conference mode.
|
|
if (ConferenceModeIsEnabled()) {
|
|
ConfigureConferenceModeScreencastCodec(&codec);
|
|
}
|
|
|
|
DisableSimulcastCodec(&codec);
|
|
}
|
|
}
|
|
|
|
*codec_out = codec;
|
|
return true;
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::SanitizeBitrates(
|
|
int channel_id, webrtc::VideoCodec* codec) {
|
|
codec->minBitrate = GetBitrate(codec->minBitrate, kMinVideoBitrate);
|
|
codec->startBitrate = GetBitrate(codec->startBitrate, kStartVideoBitrate);
|
|
codec->maxBitrate = GetBitrate(codec->maxBitrate, kMaxVideoBitrate);
|
|
|
|
if (codec->minBitrate > codec->maxBitrate) {
|
|
LOG(LS_INFO) << "Decreasing codec min bitrate to the max ("
|
|
<< codec->maxBitrate << ") because the min ("
|
|
<< codec->minBitrate << ") exceeds the max.";
|
|
codec->minBitrate = codec->maxBitrate;
|
|
}
|
|
if (codec->startBitrate < codec->minBitrate) {
|
|
LOG(LS_INFO) << "Increasing codec start bitrate to the min ("
|
|
<< codec->minBitrate << ") because the start ("
|
|
<< codec->startBitrate << ") is less than the min.";
|
|
codec->startBitrate = codec->minBitrate;
|
|
} else if (codec->startBitrate > codec->maxBitrate) {
|
|
LOG(LS_INFO) << "Decreasing codec start bitrate to the max ("
|
|
<< codec->maxBitrate << ") because the start ("
|
|
<< codec->startBitrate << ") exceeds the max.";
|
|
codec->startBitrate = codec->maxBitrate;
|
|
}
|
|
|
|
// Use a previous target bitrate, if there is one.
|
|
unsigned int current_target_bitrate = 0;
|
|
if (engine()->vie()->codec()->GetCodecTargetBitrate(
|
|
channel_id, ¤t_target_bitrate) == 0) {
|
|
// Convert to kbps.
|
|
current_target_bitrate /= 1000;
|
|
if (current_target_bitrate > codec->maxBitrate) {
|
|
current_target_bitrate = codec->maxBitrate;
|
|
}
|
|
if (current_target_bitrate > codec->startBitrate) {
|
|
codec->startBitrate = current_target_bitrate;
|
|
}
|
|
}
|
|
|
|
// Make sure the start bitrate is larger than lowest layer's min bitrate.
|
|
if (codec->numberOfSimulcastStreams > 1 &&
|
|
codec->startBitrate < codec->simulcastStream[0].minBitrate) {
|
|
codec->startBitrate = codec->simulcastStream[0].minBitrate;
|
|
}
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::OnMessage(rtc::Message* msg) {
|
|
FlushBlackFrameData* data = static_cast<FlushBlackFrameData*>(msg->pdata);
|
|
FlushBlackFrame(data->ssrc, data->timestamp, data->interval);
|
|
delete data;
|
|
}
|
|
|
|
int WebRtcVideoMediaChannel::SendPacket(int channel, const void* data,
|
|
size_t len) {
|
|
rtc::Buffer packet(data, len, kMaxRtpPacketLen);
|
|
return MediaChannel::SendPacket(&packet) ? static_cast<int>(len) : -1;
|
|
}
|
|
|
|
int WebRtcVideoMediaChannel::SendRTCPPacket(int channel,
|
|
const void* data,
|
|
size_t len) {
|
|
rtc::Buffer packet(data, len, kMaxRtpPacketLen);
|
|
return MediaChannel::SendRtcp(&packet) ? static_cast<int>(len) : -1;
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::QueueBlackFrame(uint32 ssrc, int64 timestamp,
|
|
int interval) {
|
|
if (timestamp) {
|
|
FlushBlackFrameData* black_frame_data = new FlushBlackFrameData(
|
|
ssrc, timestamp, interval);
|
|
const int delay_ms = static_cast<int>(
|
|
2 * interval * rtc::kNumMillisecsPerSec / rtc::kNumNanosecsPerSec);
|
|
worker_thread()->PostDelayed(delay_ms, this, 0, black_frame_data);
|
|
}
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::FlushBlackFrame(
|
|
uint32 ssrc, int64 timestamp, int timestamp_delta) {
|
|
WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrc(ssrc);
|
|
if (!send_channel) {
|
|
return;
|
|
}
|
|
|
|
CapturedFrameInfo last_frame_info;
|
|
if (!send_channel->last_captured_frame_info().Get(&last_frame_info))
|
|
return;
|
|
|
|
if (last_frame_info.timestamp == timestamp) {
|
|
WebRtcVideoFrame black_frame;
|
|
if (!black_frame.InitToBlack(static_cast<int>(last_frame_info.width),
|
|
static_cast<int>(last_frame_info.height), 1, 1,
|
|
last_frame_info.elapsed_time + timestamp_delta,
|
|
last_frame_info.timestamp + timestamp_delta) ||
|
|
!SendFrame(send_channel, &black_frame, last_frame_info.screencast)) {
|
|
LOG(LS_ERROR) << "Failed to send black frame.";
|
|
}
|
|
}
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::OnCpuAdaptationUnable() {
|
|
// ssrc is hardcoded to 0. This message is based on a system wide issue,
|
|
// so finding which ssrc caused it doesn't matter.
|
|
SignalMediaError(0, VideoMediaChannel::ERROR_REC_CPU_MAX_CANT_DOWNGRADE);
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::SetNetworkTransmissionState(
|
|
bool is_transmitting) {
|
|
LOG(LS_INFO) << "SetNetworkTransmissionState: " << is_transmitting;
|
|
for (SendChannelMap::iterator iter = send_channels_.begin();
|
|
iter != send_channels_.end(); ++iter) {
|
|
WebRtcVideoChannelSendInfo* send_channel = iter->second;
|
|
int channel_id = send_channel->channel_id();
|
|
engine_->vie()->network()->SetNetworkTransmissionState(channel_id,
|
|
is_transmitting);
|
|
}
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetHeaderExtension(ExtensionSetterFunction setter,
|
|
int channel_id, const RtpHeaderExtension* extension) {
|
|
bool enable = false;
|
|
int id = 0;
|
|
if (extension) {
|
|
enable = true;
|
|
id = extension->id;
|
|
}
|
|
if ((engine_->vie()->rtp()->*setter)(channel_id, enable, id) != 0) {
|
|
LOG_RTCERR4(*setter, extension->uri, channel_id, enable, id);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetHeaderExtension(ExtensionSetterFunction setter,
|
|
int channel_id, const std::vector<RtpHeaderExtension>& extensions,
|
|
const char header_extension_uri[]) {
|
|
const RtpHeaderExtension* extension = FindHeaderExtension(extensions,
|
|
header_extension_uri);
|
|
return SetHeaderExtension(setter, channel_id, extension);
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetPrimaryAndRtxSsrcs(
|
|
int channel_id, int idx, uint32 primary_ssrc,
|
|
const StreamParams& sp) {
|
|
LOG(LS_INFO) << "Set primary ssrc " << primary_ssrc
|
|
<< " on channel " << channel_id << " idx " << idx;
|
|
if (engine()->vie()->rtp()->SetLocalSSRC(
|
|
channel_id, primary_ssrc, webrtc::kViEStreamTypeNormal, idx) != 0) {
|
|
LOG_RTCERR4(SetLocalSSRC,
|
|
channel_id, primary_ssrc, webrtc::kViEStreamTypeNormal, idx);
|
|
return false;
|
|
}
|
|
|
|
uint32 rtx_ssrc = 0;
|
|
if (sp.GetFidSsrc(primary_ssrc, &rtx_ssrc)) {
|
|
LOG(LS_INFO) << "Set rtx ssrc " << rtx_ssrc
|
|
<< " on channel " << channel_id << " idx " << idx;
|
|
if (engine()->vie()->rtp()->SetLocalSSRC(
|
|
channel_id, rtx_ssrc, webrtc::kViEStreamTypeRtx, idx) != 0) {
|
|
LOG_RTCERR4(SetLocalSSRC,
|
|
channel_id, rtx_ssrc, webrtc::kViEStreamTypeRtx, idx);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetLimitedNumberOfSendSsrcs(
|
|
int channel_id, const StreamParams& sp, size_t limit) {
|
|
const SsrcGroup* sim_group = sp.get_ssrc_group(kSimSsrcGroupSemantics);
|
|
if (!sim_group || limit == 1) {
|
|
return SetPrimaryAndRtxSsrcs(channel_id, 0, sp.first_ssrc(), sp);
|
|
}
|
|
|
|
std::vector<uint32> ssrcs = sim_group->ssrcs;
|
|
for (size_t i = 0; i < ssrcs.size() && i < limit; ++i) {
|
|
if (!SetPrimaryAndRtxSsrcs(channel_id, static_cast<int>(i), ssrcs[i], sp)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WebRtcVideoMediaChannel::SetSendSsrcs(int channel_id,
|
|
const StreamParams& sp,
|
|
const webrtc::VideoCodec& codec) {
|
|
size_t limit = codec.numberOfSimulcastStreams;
|
|
return SetLimitedNumberOfSendSsrcs(channel_id, sp, limit);
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::MaybeConnectCapturer(VideoCapturer* capturer) {
|
|
if (capturer && GetSendChannelNum(capturer) == 1) {
|
|
capturer->SignalVideoFrame.connect(this,
|
|
&WebRtcVideoMediaChannel::SendFrame);
|
|
}
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::MaybeDisconnectCapturer(VideoCapturer* capturer) {
|
|
if (capturer && GetSendChannelNum(capturer) == 1) {
|
|
capturer->SignalVideoFrame.disconnect(this);
|
|
}
|
|
}
|
|
|
|
void WebRtcVideoMediaChannel::SetReceiverReportSsrc(uint32 ssrc) {
|
|
for (RecvChannelMap::const_iterator it = recv_channels_.begin();
|
|
it != recv_channels_.end(); ++it) {
|
|
int channel_id = it->second->channel_id();
|
|
if (engine()->vie()->rtp()->SetLocalSSRC(channel_id, ssrc) != 0) {
|
|
LOG_RTCERR2(SetLocalSSRC, channel_id, ssrc);
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
receiver_report_ssrc_ = ssrc;
|
|
}
|
|
|
|
} // namespace cricket
|
|
|
|
#endif // HAVE_WEBRTC_VIDEO
|