blob: cae491229fcd72b2af2a2f7faf605b251f0b98f4 [file] [log] [blame]
/*
* Copyright (c) 2012 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 "media_optimization.h"
#include "content_metrics_processing.h"
#include "frame_dropper.h"
#include "qm_select.h"
#include "modules/video_coding/main/source/tick_time_base.h"
namespace webrtc {
VCMMediaOptimization::VCMMediaOptimization(WebRtc_Word32 id,
TickTimeBase* clock):
_id(id),
_clock(clock),
_maxBitRate(0),
_sendCodecType(kVideoCodecUnknown),
_codecWidth(0),
_codecHeight(0),
_initCodecWidth(0),
_initCodecHeight(0),
_userFrameRate(0),
_packetLossEnc(0),
_fractionLost(0),
_sendStatisticsZeroEncode(0),
_maxPayloadSize(1460),
_targetBitRate(0),
_incomingFrameRate(0),
_enableQm(false),
_videoProtectionCallback(NULL),
_videoQMSettingsCallback(NULL),
_encodedFrameSamples(),
_avgSentBitRateBps(0.0f),
_keyFrameCnt(0),
_deltaFrameCnt(0),
_lastQMUpdateTime(0),
_lastChangeTime(0),
_numLayers(0)
{
memset(_sendStatistics, 0, sizeof(_sendStatistics));
memset(_incomingFrameTimes, -1, sizeof(_incomingFrameTimes));
_frameDropper = new VCMFrameDropper(_id);
_lossProtLogic = new VCMLossProtectionLogic(_clock->MillisecondTimestamp());
_content = new VCMContentMetricsProcessing();
_qmResolution = new VCMQmResolution();
}
VCMMediaOptimization::~VCMMediaOptimization(void)
{
_lossProtLogic->Release();
delete _lossProtLogic;
delete _frameDropper;
delete _content;
delete _qmResolution;
}
WebRtc_Word32
VCMMediaOptimization::Reset()
{
memset(_incomingFrameTimes, -1, sizeof(_incomingFrameTimes));
_incomingFrameRate = 0.0;
_frameDropper->Reset();
_lossProtLogic->Reset(_clock->MillisecondTimestamp());
_frameDropper->SetRates(0, 0);
_content->Reset();
_qmResolution->Reset();
_lossProtLogic->UpdateFrameRate(_incomingFrameRate);
_lossProtLogic->Reset(_clock->MillisecondTimestamp());
_sendStatisticsZeroEncode = 0;
_targetBitRate = 0;
_codecWidth = 0;
_codecHeight = 0;
_userFrameRate = 0;
_keyFrameCnt = 0;
_deltaFrameCnt = 0;
_lastQMUpdateTime = 0;
_lastChangeTime = 0;
for (WebRtc_Word32 i = 0; i < kBitrateMaxFrameSamples; i++)
{
_encodedFrameSamples[i]._sizeBytes = -1;
_encodedFrameSamples[i]._timeCompleteMs = -1;
}
_avgSentBitRateBps = 0.0f;
_numLayers = 1;
return VCM_OK;
}
WebRtc_UWord32
VCMMediaOptimization::SetTargetRates(WebRtc_UWord32 bitRate,
WebRtc_UWord8 &fractionLost,
WebRtc_UWord32 roundTripTimeMs)
{
VCMProtectionMethod *selectedMethod = _lossProtLogic->SelectedMethod();
_lossProtLogic->UpdateBitRate(static_cast<float>(bitRate));
_lossProtLogic->UpdateRtt(roundTripTimeMs);
_lossProtLogic->UpdateResidualPacketLoss(static_cast<float>(fractionLost));
// Get frame rate for encoder: this is the actual/sent frame rate
float actualFrameRate = SentFrameRate();
// sanity
if (actualFrameRate < 1.0)
{
actualFrameRate = 1.0;
}
// Update frame rate for the loss protection logic class: frame rate should
// be the actual/sent rate
_lossProtLogic->UpdateFrameRate(actualFrameRate);
_fractionLost = fractionLost;
// Returns the filtered packet loss, used for the protection setting.
// The filtered loss may be the received loss (no filter), or some
// filtered value (average or max window filter).
// Use max window filter for now.
FilterPacketLossMode filter_mode = kMaxFilter;
WebRtc_UWord8 packetLossEnc = _lossProtLogic->FilteredLoss(
_clock->MillisecondTimestamp(), filter_mode, fractionLost);
// For now use the filtered loss for computing the robustness settings
_lossProtLogic->UpdateFilteredLossPr(packetLossEnc);
// Rate cost of the protection methods
uint32_t protection_overhead_kbps = 0;
// Update protection settings, when applicable
float sent_video_rate = 0.0f;
if (selectedMethod)
{
// Update protection method with content metrics
selectedMethod->UpdateContentMetrics(_content->ShortTermAvgData());
// Update method will compute the robustness settings for the given
// protection method and the overhead cost
// the protection method is set by the user via SetVideoProtection.
_lossProtLogic->UpdateMethod();
// Update protection callback with protection settings.
uint32_t sent_video_rate_bps = 0;
uint32_t sent_nack_rate_bps = 0;
uint32_t sent_fec_rate_bps = 0;
// Get the bit cost of protection method, based on the amount of
// overhead data actually transmitted (including headers) the last
// second.
UpdateProtectionCallback(selectedMethod,
&sent_video_rate_bps,
&sent_nack_rate_bps,
&sent_fec_rate_bps);
uint32_t sent_total_rate_bps = sent_video_rate_bps +
sent_nack_rate_bps + sent_fec_rate_bps;
// Estimate the overhead costs of the next second as staying the same
// wrt the source bitrate.
if (sent_total_rate_bps > 0) {
protection_overhead_kbps = static_cast<uint32_t>(bitRate *
static_cast<double>(sent_nack_rate_bps + sent_fec_rate_bps) /
sent_total_rate_bps + 0.5);
}
// Cap the overhead estimate to 50%.
if (protection_overhead_kbps > bitRate / 2)
protection_overhead_kbps = bitRate / 2;
// Get the effective packet loss for encoder ER
// when applicable, should be passed to encoder via fractionLost
packetLossEnc = selectedMethod->RequiredPacketLossER();
sent_video_rate = static_cast<float>(sent_video_rate_bps / 1000.0);
}
// Source coding rate: total rate - protection overhead
_targetBitRate = bitRate - protection_overhead_kbps;
// Update encoding rates following protection settings
_frameDropper->SetRates(static_cast<float>(_targetBitRate), 0);
if (_enableQm && _numLayers == 1)
{
// Update QM with rates
_qmResolution->UpdateRates((float)_targetBitRate, sent_video_rate,
_incomingFrameRate, _fractionLost);
// Check for QM selection
bool selectQM = checkStatusForQMchange();
if (selectQM)
{
SelectQuality();
}
// Reset the short-term averaged content data.
_content->ResetShortTermAvgData();
}
return _targetBitRate;
}
int VCMMediaOptimization::UpdateProtectionCallback(
VCMProtectionMethod *selected_method,
uint32_t* video_rate_bps,
uint32_t* nack_overhead_rate_bps,
uint32_t* fec_overhead_rate_bps)
{
if (!_videoProtectionCallback)
{
return VCM_OK;
}
// Get the FEC code rate for Key frames (set to 0 when NA)
const WebRtc_UWord8
codeRateKeyRTP = selected_method->RequiredProtectionFactorK();
// Get the FEC code rate for Delta frames (set to 0 when NA)
const WebRtc_UWord8
codeRateDeltaRTP = selected_method->RequiredProtectionFactorD();
// Get the FEC-UEP protection status for Key frames: UEP on/off
const bool
useUepProtectionKeyRTP = selected_method->RequiredUepProtectionK();
// Get the FEC-UEP protection status for Delta frames: UEP on/off
const bool
useUepProtectionDeltaRTP = selected_method->RequiredUepProtectionD();
// NACK is on for NACK and NackFec protection method: off for FEC method
bool nackStatus = (selected_method->Type() == kNackFec ||
selected_method->Type() == kNack);
// TODO(Marco): Pass FEC protection values per layer.
return _videoProtectionCallback->ProtectionRequest(codeRateDeltaRTP,
codeRateKeyRTP,
useUepProtectionDeltaRTP,
useUepProtectionKeyRTP,
nackStatus,
video_rate_bps,
nack_overhead_rate_bps,
fec_overhead_rate_bps);
}
bool
VCMMediaOptimization::DropFrame()
{
// leak appropriate number of bytes
_frameDropper->Leak((WebRtc_UWord32)(InputFrameRate() + 0.5f));
return _frameDropper->DropFrame();
}
WebRtc_Word32
VCMMediaOptimization::SentFrameCount(VCMFrameCount &frameCount) const
{
frameCount.numDeltaFrames = _deltaFrameCnt;
frameCount.numKeyFrames = _keyFrameCnt;
return VCM_OK;
}
WebRtc_Word32
VCMMediaOptimization::SetEncodingData(VideoCodecType sendCodecType,
WebRtc_Word32 maxBitRate,
WebRtc_UWord32 frameRate,
WebRtc_UWord32 bitRate,
WebRtc_UWord16 width,
WebRtc_UWord16 height,
int numLayers)
{
// Everything codec specific should be reset here since this means the codec
// has changed. If native dimension values have changed, then either user
// initiated change, or QM initiated change. Will be able to determine only
// after the processing of the first frame.
_lastChangeTime = _clock->MillisecondTimestamp();
_content->Reset();
_content->UpdateFrameRate(frameRate);
_maxBitRate = maxBitRate;
_sendCodecType = sendCodecType;
_targetBitRate = bitRate;
_lossProtLogic->UpdateBitRate(static_cast<float>(bitRate));
_lossProtLogic->UpdateFrameRate(static_cast<float>(frameRate));
_lossProtLogic->UpdateFrameSize(width, height);
_lossProtLogic->UpdateNumLayers(numLayers);
_frameDropper->Reset();
_frameDropper->SetRates(static_cast<float>(bitRate),
static_cast<float>(frameRate));
_userFrameRate = static_cast<float>(frameRate);
_codecWidth = width;
_codecHeight = height;
_initCodecWidth = width;
_initCodecHeight = height;
_numLayers = (numLayers <= 1) ? 1 : numLayers; // Can also be zero.
WebRtc_Word32 ret = VCM_OK;
ret = _qmResolution->Initialize((float)_targetBitRate, _userFrameRate,
_codecWidth, _codecHeight);
return ret;
}
WebRtc_Word32
VCMMediaOptimization::RegisterProtectionCallback(VCMProtectionCallback*
protectionCallback)
{
_videoProtectionCallback = protectionCallback;
return VCM_OK;
}
void
VCMMediaOptimization::EnableFrameDropper(bool enable)
{
_frameDropper->Enable(enable);
}
void
VCMMediaOptimization::EnableProtectionMethod(bool enable,
VCMProtectionMethodEnum method)
{
bool updated = false;
if (enable)
{
updated = _lossProtLogic->SetMethod(method);
}
else
{
_lossProtLogic->RemoveMethod(method);
}
if (updated)
{
_lossProtLogic->UpdateMethod();
}
}
bool
VCMMediaOptimization::IsProtectionMethodEnabled(VCMProtectionMethodEnum method)
{
return (_lossProtLogic->SelectedType() == method);
}
void
VCMMediaOptimization::SetMtu(WebRtc_Word32 mtu)
{
_maxPayloadSize = mtu;
}
float
VCMMediaOptimization::SentFrameRate()
{
if (_frameDropper)
{
return _frameDropper->ActualFrameRate((WebRtc_UWord32)(InputFrameRate()
+ 0.5f));
}
return VCM_CODEC_ERROR;
}
float
VCMMediaOptimization::SentBitRate()
{
UpdateBitRateEstimate(-1, _clock->MillisecondTimestamp());
return _avgSentBitRateBps / 1000.0f;
}
WebRtc_Word32
VCMMediaOptimization::MaxBitRate()
{
return _maxBitRate;
}
WebRtc_Word32
VCMMediaOptimization::UpdateWithEncodedData(WebRtc_Word32 encodedLength,
FrameType encodedFrameType)
{
// look into the ViE version - debug mode - needs also number of layers.
UpdateBitRateEstimate(encodedLength, _clock->MillisecondTimestamp());
if(encodedLength > 0)
{
const bool deltaFrame = (encodedFrameType != kVideoFrameKey &&
encodedFrameType != kVideoFrameGolden);
_frameDropper->Fill(encodedLength, deltaFrame);
if (_maxPayloadSize > 0 && encodedLength > 0)
{
const float minPacketsPerFrame = encodedLength /
static_cast<float>(_maxPayloadSize);
if (deltaFrame)
{
_lossProtLogic->UpdatePacketsPerFrame(
minPacketsPerFrame, _clock->MillisecondTimestamp());
}
else
{
_lossProtLogic->UpdatePacketsPerFrameKey(
minPacketsPerFrame, _clock->MillisecondTimestamp());
}
if (_enableQm)
{
// update quality select with encoded length
_qmResolution->UpdateEncodedSize(encodedLength,
encodedFrameType);
}
}
if (!deltaFrame && encodedLength > 0)
{
_lossProtLogic->UpdateKeyFrameSize(static_cast<float>(encodedLength));
}
// updating counters
if (deltaFrame)
{
_deltaFrameCnt++;
}
else
{
_keyFrameCnt++;
}
}
return VCM_OK;
}
void VCMMediaOptimization::UpdateBitRateEstimate(WebRtc_Word64 encodedLength,
WebRtc_Word64 nowMs)
{
int i = kBitrateMaxFrameSamples - 1;
WebRtc_UWord32 frameSizeSum = 0;
WebRtc_Word64 timeOldest = -1;
// Find an empty slot for storing the new sample and at the same time
// accumulate the history.
for (; i >= 0; i--)
{
if (_encodedFrameSamples[i]._sizeBytes == -1)
{
// Found empty slot
break;
}
if (nowMs - _encodedFrameSamples[i]._timeCompleteMs <
kBitrateAverageWinMs)
{
frameSizeSum += static_cast<WebRtc_UWord32>
(_encodedFrameSamples[i]._sizeBytes);
if (timeOldest == -1)
{
timeOldest = _encodedFrameSamples[i]._timeCompleteMs;
}
}
}
if (encodedLength > 0)
{
if (i < 0)
{
// No empty slot, shift
for (i = kBitrateMaxFrameSamples - 2; i >= 0; i--)
{
_encodedFrameSamples[i + 1] = _encodedFrameSamples[i];
}
i++;
}
// Insert new sample
_encodedFrameSamples[i]._sizeBytes = encodedLength;
_encodedFrameSamples[i]._timeCompleteMs = nowMs;
}
if (timeOldest > -1)
{
// Update average bit rate
float denom = static_cast<float>(nowMs - timeOldest);
if (denom < 1.0)
{
denom = 1.0;
}
_avgSentBitRateBps = (frameSizeSum + encodedLength) * 8 * 1000 / denom;
}
else if (encodedLength > 0)
{
_avgSentBitRateBps = static_cast<float>(encodedLength * 8);
}
else
{
_avgSentBitRateBps = 0;
}
}
WebRtc_Word32
VCMMediaOptimization::RegisterVideoQMCallback(VCMQMSettingsCallback*
videoQMSettings)
{
_videoQMSettingsCallback = videoQMSettings;
// Callback setting controls QM
if (_videoQMSettingsCallback != NULL)
{
_enableQm = true;
}
else
{
_enableQm = false;
}
return VCM_OK;
}
void
VCMMediaOptimization::updateContentData(const VideoContentMetrics*
contentMetrics)
{
// Updating content metrics
if (contentMetrics == NULL)
{
// Disable QM if metrics are NULL
_enableQm = false;
_qmResolution->Reset();
}
else
{
_content->UpdateContentData(contentMetrics);
}
}
WebRtc_Word32
VCMMediaOptimization::SelectQuality()
{
// Reset quantities for QM select
_qmResolution->ResetQM();
// Update QM will long-term averaged content metrics.
_qmResolution->UpdateContent(_content->LongTermAvgData());
// Select quality mode
VCMResolutionScale* qm = NULL;
WebRtc_Word32 ret = _qmResolution->SelectResolution(&qm);
if (ret < 0)
{
return ret;
}
// Check for updates to spatial/temporal modes
QMUpdate(qm);
// Reset all the rate and related frame counters quantities
_qmResolution->ResetRates();
// Reset counters
_lastQMUpdateTime = _clock->MillisecondTimestamp();
// Reset content metrics
_content->Reset();
return VCM_OK;
}
// Check timing constraints and look for significant change in:
// (1) scene content
// (2) target bit rate
bool
VCMMediaOptimization::checkStatusForQMchange()
{
bool status = true;
// Check that we do not call QMSelect too often, and that we waited some time
// (to sample the metrics) from the event lastChangeTime
// lastChangeTime is the time where user changed the size/rate/frame rate
// (via SetEncodingData)
WebRtc_Word64 now = _clock->MillisecondTimestamp();
if ((now - _lastQMUpdateTime) < kQmMinIntervalMs ||
(now - _lastChangeTime) < kQmMinIntervalMs)
{
status = false;
}
return status;
}
bool
VCMMediaOptimization::QMUpdate(VCMResolutionScale* qm)
{
// Check for no change
if (qm->spatialHeightFact == 1 &&
qm->spatialWidthFact == 1 &&
qm->temporalFact == 1) {
return false;
}
// Temporal
WebRtc_UWord32 frameRate = static_cast<WebRtc_UWord32>
(_incomingFrameRate + 0.5f);
// Check if go back up in temporal resolution
if (qm->temporalFact == 0) {
// Currently only allow for 1/2 frame rate reduction per action.
// TODO (marpan): allow for 2/3 reduction.
frameRate = (WebRtc_UWord32) 2 * _incomingFrameRate;
}
// go down in temporal resolution
else {
frameRate = (WebRtc_UWord32)(_incomingFrameRate / qm->temporalFact + 1);
}
// Reset _incomingFrameRate if temporal action was selected.
if (qm->temporalFact != 1) {
memset(_incomingFrameTimes, -1, sizeof(_incomingFrameTimes));
_incomingFrameRate = frameRate;
}
// Spatial
WebRtc_UWord32 height = _codecHeight;
WebRtc_UWord32 width = _codecWidth;
// Check if go back up in spatial resolution, and update frame sizes.
// Currently only allow for 2x2 spatial down-sampling.
// TODO (marpan): allow for 1x2, 2x1, and 4/3x4/3 (or 3/2x3/2).
if (qm->spatialHeightFact == 0 && qm->spatialWidthFact == 0) {
width = _codecWidth * 2;
height = _codecHeight * 2;
} else {
width = _codecWidth / qm->spatialWidthFact;
height = _codecHeight / qm->spatialHeightFact;
}
_codecWidth = width;
_codecHeight = height;
// New frame sizes should never exceed the original sizes
// from SetEncodingData().
assert(_codecWidth <= _initCodecWidth);
assert(_codecHeight <= _initCodecHeight);
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, _id,
"Quality Mode Update: W = %d, H = %d, FR = %f",
width, height, frameRate);
// Update VPM with new target frame rate and size
_videoQMSettingsCallback->SetVideoQMSettings(frameRate, width, height);
_content->UpdateFrameRate(frameRate);
_qmResolution->UpdateCodecFrameSize(width, height);
return true;
}
void
VCMMediaOptimization::UpdateIncomingFrameRate()
{
WebRtc_Word64 now = _clock->MillisecondTimestamp();
if (_incomingFrameTimes[0] == 0)
{
// first no shift
} else
{
// shift
for(WebRtc_Word32 i = (kFrameCountHistorySize - 2); i >= 0 ; i--)
{
_incomingFrameTimes[i+1] = _incomingFrameTimes[i];
}
}
_incomingFrameTimes[0] = now;
ProcessIncomingFrameRate(now);
}
// allowing VCM to keep track of incoming frame rate
void
VCMMediaOptimization::ProcessIncomingFrameRate(WebRtc_Word64 now)
{
WebRtc_Word32 num = 0;
WebRtc_Word32 nrOfFrames = 0;
for (num = 1; num < (kFrameCountHistorySize - 1); num++)
{
if (_incomingFrameTimes[num] <= 0 ||
// don't use data older than 2 s
now - _incomingFrameTimes[num] > kFrameHistoryWinMs)
{
break;
} else
{
nrOfFrames++;
}
}
if (num > 1)
{
const WebRtc_Word64 diff = now - _incomingFrameTimes[num-1];
_incomingFrameRate = 1.0;
if(diff >0)
{
_incomingFrameRate = nrOfFrames * 1000.0f / static_cast<float>(diff);
}
}
}
WebRtc_UWord32
VCMMediaOptimization::InputFrameRate()
{
ProcessIncomingFrameRate(_clock->MillisecondTimestamp());
return WebRtc_UWord32 (_incomingFrameRate + 0.5f);
}
}