| /* |
| * 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/webrtctexturevideoframe.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. |
| virtual webrtc::VideoEncoder* Create() OVERRIDE { |
| return factory_->CreateVideoEncoder(webrtc::kVideoCodecVP8); |
| } |
| |
| virtual 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 kExternalVideoPayloadTypeBase = 120; |
| |
| 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(int index) { |
| #if ENABLE_DEBUG |
| static const int kMaxExternalVideoCodecs = 8; |
| ASSERT(index >= 0 && index < kMaxExternalVideoCodecs); |
| #endif |
| return kExternalVideoPayloadTypeBase + 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) { |
| return codecs.size() == 1 && codecs[0].type == webrtc::kVideoCodecVP8; |
| } |
| |
| webrtc::VideoEncoder* WebRtcSimulcastEncoderFactory::CreateVideoEncoder( |
| webrtc::VideoCodecType type) { |
| ASSERT(type == webrtc::kVideoCodecVP8); |
| ASSERT(factory_ != NULL); |
| return new webrtc::SimulcastEncoderAdapter( |
| new EncoderFactoryAdapter(factory_)); |
| } |
| |
| const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& |
| WebRtcSimulcastEncoderFactory::codecs() const { |
| return factory_->codecs(); |
| } |
| |
| void WebRtcSimulcastEncoderFactory::DestroyVideoEncoder( |
| webrtc::VideoEncoder* encoder) { |
| 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; |
| } |
| |
| WebRtcVideoRenderFrame cricket_frame(&webrtc_frame, elapsed_time_ms); |
| 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. |
| virtual void OveruseDetected() OVERRIDE { |
| rtc::CritScope cs(&crit_); |
| if (!enabled_) { |
| return; |
| } |
| |
| video_adapter_->OnCpuResolutionRequest(CoordinatedVideoAdapter::DOWNGRADE); |
| } |
| |
| virtual 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. |
| // video_adapter_.SetBlackOutput(on); |
| 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() { |
| Construct(new ViEWrapper(), new ViETraceWrapper(), NULL, |
| new rtc::CpuMonitor(NULL)); |
| } |
| |
| WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine, |
| ViEWrapper* vie_wrapper, |
| rtc::CpuMonitor* cpu_monitor) { |
| Construct(vie_wrapper, new ViETraceWrapper(), voice_engine, cpu_monitor); |
| } |
| |
| WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine, |
| ViEWrapper* vie_wrapper, |
| ViETraceWrapper* tracing, |
| rtc::CpuMonitor* cpu_monitor) { |
| 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); |
| voice_engine_ = voice_engine; |
| 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(this) != 0) { |
| LOG_RTCERR1(SetTraceCallback, this); |
| } |
| |
| 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(static_cast<int>(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(static_cast<int>(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::SetVoiceEngine(WebRtcVoiceEngine* voice_engine) { |
| if (initialized_) { |
| LOG(LS_WARNING) << "SetVoiceEngine can not be called after Init"; |
| return false; |
| } |
| voice_engine_ = voice_engine; |
| return true; |
| } |
| |
| bool WebRtcVideoEngine::EnableTimedRender() { |
| if (initialized_) { |
| LOG(LS_WARNING) << "EnableTimedRender can not be called after Init"; |
| return false; |
| } |
| render_module_.reset(webrtc::VideoRender::CreateVideoRender(0, NULL, |
| false, webrtc::kRenderExternal)); |
| return true; |
| } |
| |
| void WebRtcVideoEngine::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(static_cast<int>(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; |
| } |
| |
| // Ignore spammy trace messages, mostly from the stats API when we haven't |
| // gotten RTCP info yet from the remote side. |
| bool WebRtcVideoEngine::ShouldIgnoreTrace(const std::string& trace) { |
| static const char* const kTracesToIgnore[] = { |
| NULL |
| }; |
| for (const char* const* p = kTracesToIgnore; *p; ++p) { |
| if (trace.find(*p) == 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| int WebRtcVideoEngine::GetNumOfChannels() { |
| rtc::CritScope cs(&channels_crit_); |
| return static_cast<int>(channels_.size()); |
| } |
| |
| void WebRtcVideoEngine::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 (!ShouldIgnoreTrace(msg) && |
| (!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) { |
| if (!encoder_factory_) { |
| return NULL; |
| } |
| 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(static_cast<int>(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; |
| } |
| |
| webrtc::VideoEncoder* encoder = |
| engine()->CreateExternalEncoder(codec.codecType); |
| 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, false) != 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(const StatsOptions& options, |
| 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; |
| |
| // TODO(jiayl): remove the condition when the necessary changes are available |
| // outside the dev branch. |
| if (options.include_received_propagation_stats) { |
| webrtc::ReceiveBandwidthEstimatorStats additional_stats; |
| // Only call for the default channel because the returned stats are |
| // collected for all the channels using the same estimator. |
| if (engine_->vie()->rtp()->GetReceiveBandwidthEstimatorStats( |
| GetDefaultRecvChannel()->channel_id(), &additional_stats) == 0) { |
| bwe.total_received_propagation_delta_ms = |
| additional_stats.total_propagation_time_delta_ms; |
| bwe.recent_received_propagation_delta_ms.swap( |
| additional_stats.recent_propagation_time_delta_ms); |
| bwe.recent_received_packet_group_arrival_time_ms.swap( |
| additional_stats.recent_arrival_time_ms); |
| } |
| } |
| |
| 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::ViEVideoFrameI420 frame_i420; |
| // TODO(ronghuawu): Update the webrtc::ViEVideoFrameI420 |
| // to use const unsigned char* |
| frame_i420.y_plane = const_cast<unsigned char*>(frame_out->GetYPlane()); |
| frame_i420.u_plane = const_cast<unsigned char*>(frame_out->GetUPlane()); |
| frame_i420.v_plane = const_cast<unsigned char*>(frame_out->GetVPlane()); |
| frame_i420.y_pitch = frame_out->GetYPitch(); |
| frame_i420.u_pitch = frame_out->GetUPitch(); |
| frame_i420.v_pitch = frame_out->GetVPitch(); |
| frame_i420.width = static_cast<uint16>(frame_out->GetWidth()); |
| frame_i420.height = static_cast<uint16>(frame_out->GetHeight()); |
| |
| 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 |
| |
| return send_channel->external_capture()->IncomingFrameI420( |
| frame_i420, timestamp_ntp_ms) == 0; |
| } |
| |
| 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 |