This CL removes MediaOptmization and folds some of its functionality into VideoStreamEncoder. The FPS tracking is now handled by a RateStatistics instance. Frame dropping is still handled by FrameDropper. Both of these now live directly in VideoStreamEncoder. There is no intended change in behavior from this CL, but due to a new way of measuring frame rate, some minor perf changes can be expected. A small change in behavior is that OnBitrateUpdated is now called directly rather than on the next frame. Since both encoding frame and setting rate allocations happen on the encoder worker thread, there's really no reason to cache bitrates and wait until the next frame. An edge case though is that if a new bitrate is set before the first frame, we must remember that bitrate and then apply it after the video bitrate allocator has been first created. In addition to existing unit tests, manual tests have been used to confirm that frame dropping works as expected with misbehaving encoders. Bug: webrtc:10164 Change-Id: I7ee9c8d3c4f2bcf23c8c420310b05a4d35d94744 Reviewed-on: https://webrtc-review.googlesource.com/c/115620 Commit-Queue: Erik Språng <sprang@webrtc.org> Reviewed-by: Niels Moller <nisse@webrtc.org> Cr-Commit-Position: refs/heads/master@{#26147}
262 lines
9.8 KiB
C++
262 lines
9.8 KiB
C++
/*
|
|
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <vector>
|
|
|
|
#include "api/video/video_bitrate_allocation.h"
|
|
#include "api/video/video_bitrate_allocator.h"
|
|
#include "api/video/video_frame.h"
|
|
#include "api/video/video_frame_buffer.h"
|
|
#include "api/video_codecs/video_codec.h"
|
|
#include "api/video_codecs/video_encoder.h"
|
|
#include "common_types.h" // NOLINT(build/include)
|
|
#include "modules/video_coding/encoder_database.h"
|
|
#include "modules/video_coding/generic_encoder.h"
|
|
#include "modules/video_coding/include/video_codec_interface.h"
|
|
#include "modules/video_coding/include/video_coding_defines.h"
|
|
#include "modules/video_coding/include/video_error_codes.h"
|
|
#include "modules/video_coding/internal_defines.h"
|
|
#include "modules/video_coding/utility/default_video_bitrate_allocator.h"
|
|
#include "modules/video_coding/video_coding_impl.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/criticalsection.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/scoped_ref_ptr.h"
|
|
#include "rtc_base/sequenced_task_checker.h"
|
|
#include "system_wrappers/include/clock.h"
|
|
#include "system_wrappers/include/field_trial.h"
|
|
|
|
namespace webrtc {
|
|
namespace vcm {
|
|
|
|
VideoSender::VideoSender(Clock* clock,
|
|
EncodedImageCallback* post_encode_callback)
|
|
: _encoder(nullptr),
|
|
_encodedFrameCallback(post_encode_callback),
|
|
_codecDataBase(&_encodedFrameCallback),
|
|
current_codec_(),
|
|
encoder_has_internal_source_(false),
|
|
next_frame_types_(1, kVideoFrameDelta) {
|
|
// Allow VideoSender to be created on one thread but used on another, post
|
|
// construction. This is currently how this class is being used by at least
|
|
// one external project (diffractor).
|
|
sequenced_checker_.Detach();
|
|
}
|
|
|
|
VideoSender::~VideoSender() {}
|
|
|
|
// Register the send codec to be used.
|
|
int32_t VideoSender::RegisterSendCodec(const VideoCodec* sendCodec,
|
|
uint32_t numberOfCores,
|
|
uint32_t maxPayloadSize) {
|
|
RTC_DCHECK(sequenced_checker_.CalledSequentially());
|
|
rtc::CritScope lock(&encoder_crit_);
|
|
if (sendCodec == nullptr) {
|
|
return VCM_PARAMETER_ERROR;
|
|
}
|
|
|
|
bool ret =
|
|
_codecDataBase.SetSendCodec(sendCodec, numberOfCores, maxPayloadSize);
|
|
|
|
// Update encoder regardless of result to make sure that we're not holding on
|
|
// to a deleted instance.
|
|
_encoder = _codecDataBase.GetEncoder();
|
|
// Cache the current codec here so they can be fetched from this thread
|
|
// without requiring the _sendCritSect lock.
|
|
current_codec_ = *sendCodec;
|
|
|
|
if (!ret) {
|
|
RTC_LOG(LS_ERROR) << "Failed to initialize set encoder with codec type '"
|
|
<< sendCodec->codecType << "'.";
|
|
return VCM_CODEC_ERROR;
|
|
}
|
|
|
|
// SetSendCodec succeeded, _encoder should be set.
|
|
RTC_DCHECK(_encoder);
|
|
|
|
{
|
|
rtc::CritScope cs(¶ms_crit_);
|
|
next_frame_types_.clear();
|
|
next_frame_types_.resize(VCM_MAX(sendCodec->numberOfSimulcastStreams, 1),
|
|
kVideoFrameKey);
|
|
// Cache InternalSource() to have this available from IntraFrameRequest()
|
|
// without having to acquire encoder_crit_ (avoid blocking on encoder use).
|
|
encoder_has_internal_source_ = _encoder->InternalSource();
|
|
}
|
|
|
|
RTC_LOG(LS_VERBOSE) << " max bitrate " << sendCodec->maxBitrate
|
|
<< " start bitrate " << sendCodec->startBitrate
|
|
<< " max frame rate " << sendCodec->maxFramerate
|
|
<< " max payload size " << maxPayloadSize;
|
|
return VCM_OK;
|
|
}
|
|
|
|
// Register an external decoder object.
|
|
// This can not be used together with external decoder callbacks.
|
|
void VideoSender::RegisterExternalEncoder(VideoEncoder* externalEncoder,
|
|
bool internalSource /*= false*/) {
|
|
RTC_DCHECK(sequenced_checker_.CalledSequentially());
|
|
|
|
rtc::CritScope lock(&encoder_crit_);
|
|
|
|
if (externalEncoder == nullptr) {
|
|
_codecDataBase.DeregisterExternalEncoder();
|
|
{
|
|
// Make sure the VCM doesn't use the de-registered codec
|
|
rtc::CritScope params_lock(¶ms_crit_);
|
|
_encoder = nullptr;
|
|
encoder_has_internal_source_ = false;
|
|
}
|
|
return;
|
|
}
|
|
_codecDataBase.RegisterExternalEncoder(externalEncoder,
|
|
internalSource);
|
|
}
|
|
|
|
int32_t VideoSender::SetChannelParameters(
|
|
const VideoBitrateAllocation& bitrate_allocation,
|
|
uint32_t framerate_fps) {
|
|
bool encoder_has_internal_source;
|
|
{
|
|
rtc::CritScope cs(¶ms_crit_);
|
|
encoder_has_internal_source = encoder_has_internal_source_;
|
|
}
|
|
|
|
{
|
|
rtc::CritScope cs(&encoder_crit_);
|
|
if (_encoder) {
|
|
// |target_bitrate == 0 | means that the network is down or the send pacer
|
|
// is full. We currently only report this if the encoder has an internal
|
|
// source. If the encoder does not have an internal source, higher levels
|
|
// are expected to not call AddVideoFrame. We do this since its unclear
|
|
// how current encoder implementations behave when given a zero target
|
|
// bitrate.
|
|
// TODO(perkj): Make sure all known encoder implementations handle zero
|
|
// target bitrate and remove this check.
|
|
if (!encoder_has_internal_source &&
|
|
bitrate_allocation.get_sum_bps() == 0) {
|
|
return VCM_OK;
|
|
}
|
|
|
|
if (framerate_fps == 0) {
|
|
// No frame rate estimate available, use default.
|
|
framerate_fps = current_codec_.maxFramerate;
|
|
}
|
|
if (_encoder != nullptr)
|
|
_encoder->SetEncoderParameters(bitrate_allocation, framerate_fps);
|
|
}
|
|
}
|
|
|
|
return VCM_OK;
|
|
}
|
|
|
|
// Add one raw video frame to the encoder, blocking.
|
|
int32_t VideoSender::AddVideoFrame(
|
|
const VideoFrame& videoFrame,
|
|
const CodecSpecificInfo* codecSpecificInfo,
|
|
absl::optional<VideoEncoder::EncoderInfo> encoder_info) {
|
|
std::vector<FrameType> next_frame_types;
|
|
bool encoder_has_internal_source = false;
|
|
{
|
|
rtc::CritScope lock(¶ms_crit_);
|
|
next_frame_types = next_frame_types_;
|
|
encoder_has_internal_source = encoder_has_internal_source_;
|
|
}
|
|
rtc::CritScope lock(&encoder_crit_);
|
|
if (_encoder == nullptr)
|
|
return VCM_UNINITIALIZED;
|
|
// TODO(pbos): Make sure setting send codec is synchronized with video
|
|
// processing so frame size always matches.
|
|
if (!_codecDataBase.MatchesCurrentResolution(videoFrame.width(),
|
|
videoFrame.height())) {
|
|
RTC_LOG(LS_ERROR)
|
|
<< "Incoming frame doesn't match set resolution. Dropping.";
|
|
return VCM_PARAMETER_ERROR;
|
|
}
|
|
VideoFrame converted_frame = videoFrame;
|
|
const VideoFrameBuffer::Type buffer_type =
|
|
converted_frame.video_frame_buffer()->type();
|
|
const bool is_buffer_type_supported =
|
|
buffer_type == VideoFrameBuffer::Type::kI420 ||
|
|
(buffer_type == VideoFrameBuffer::Type::kNative &&
|
|
encoder_info->supports_native_handle);
|
|
if (!is_buffer_type_supported) {
|
|
// This module only supports software encoding.
|
|
// TODO(pbos): Offload conversion from the encoder thread.
|
|
rtc::scoped_refptr<I420BufferInterface> converted_buffer(
|
|
converted_frame.video_frame_buffer()->ToI420());
|
|
|
|
if (!converted_buffer) {
|
|
RTC_LOG(LS_ERROR) << "Frame conversion failed, dropping frame.";
|
|
return VCM_PARAMETER_ERROR;
|
|
}
|
|
converted_frame = VideoFrame::Builder()
|
|
.set_video_frame_buffer(converted_buffer)
|
|
.set_timestamp_rtp(converted_frame.timestamp())
|
|
.set_timestamp_ms(converted_frame.render_time_ms())
|
|
.set_rotation(converted_frame.rotation())
|
|
.set_id(converted_frame.id())
|
|
.build();
|
|
}
|
|
int32_t ret =
|
|
_encoder->Encode(converted_frame, codecSpecificInfo, next_frame_types);
|
|
if (ret < 0) {
|
|
RTC_LOG(LS_ERROR) << "Failed to encode frame. Error code: " << ret;
|
|
return ret;
|
|
}
|
|
|
|
{
|
|
rtc::CritScope lock(¶ms_crit_);
|
|
// Change all keyframe requests to encode delta frames the next time.
|
|
for (size_t i = 0; i < next_frame_types_.size(); ++i) {
|
|
// Check for equality (same requested as before encoding) to not
|
|
// accidentally drop a keyframe request while encoding.
|
|
if (next_frame_types[i] == next_frame_types_[i])
|
|
next_frame_types_[i] = kVideoFrameDelta;
|
|
}
|
|
}
|
|
return VCM_OK;
|
|
}
|
|
|
|
int32_t VideoSender::IntraFrameRequest(size_t stream_index) {
|
|
{
|
|
rtc::CritScope lock(¶ms_crit_);
|
|
if (stream_index >= next_frame_types_.size()) {
|
|
return -1;
|
|
}
|
|
next_frame_types_[stream_index] = kVideoFrameKey;
|
|
if (!encoder_has_internal_source_)
|
|
return VCM_OK;
|
|
}
|
|
// TODO(pbos): Remove when InternalSource() is gone. Both locks have to be
|
|
// held here for internal consistency, since _encoder could be removed while
|
|
// not holding encoder_crit_. Checks have to be performed again since
|
|
// params_crit_ was dropped to not cause lock-order inversions with
|
|
// encoder_crit_.
|
|
rtc::CritScope lock(&encoder_crit_);
|
|
rtc::CritScope params_lock(¶ms_crit_);
|
|
if (stream_index >= next_frame_types_.size())
|
|
return -1;
|
|
if (_encoder != nullptr && _encoder->InternalSource()) {
|
|
// Try to request the frame if we have an external encoder with
|
|
// internal source since AddVideoFrame never will be called.
|
|
if (_encoder->RequestFrame(next_frame_types_) == WEBRTC_VIDEO_CODEC_OK) {
|
|
// Try to remove just-performed keyframe request, if stream still exists.
|
|
next_frame_types_[stream_index] = kVideoFrameDelta;
|
|
}
|
|
}
|
|
return VCM_OK;
|
|
}
|
|
|
|
} // namespace vcm
|
|
} // namespace webrtc
|