/*
 *  Copyright (c) 2011 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.
 */

/*
 * ViEEncoder.cpp
 */

#include "vie_encoder.h"
#include "vie_defines.h"

#include "critical_section_wrapper.h"
#include "process_thread.h"
#include "rtp_rtcp.h"
#include "video_coding.h"
#include "video_coding_defines.h"
#include "video_codec_interface.h"
#include "vie_codec.h"
#include "vie_image_process.h"
#include "tick_util.h"
#include "trace.h"

#include <cassert>
namespace webrtc {

// ----------------------------------------------------------------------------
// Constructor
// ----------------------------------------------------------------------------

ViEEncoder::ViEEncoder(WebRtc_Word32 engineId, WebRtc_Word32 channelId,
                       WebRtc_UWord32 numberOfCores,
                       ProcessThread& moduleProcessThread)
       :
        _engineId(engineId),
        _channelId(channelId),
        _numberOfCores(numberOfCores),
        _vcm(*webrtc::VideoCodingModule::Create(ViEModuleId(engineId,
                                                            channelId))),
        _vpm(*webrtc::VideoProcessingModule::Create(ViEModuleId(engineId,
                                                                channelId))),
        _defaultRtpRtcp(*RtpRtcp::CreateRtpRtcp(
            ViEModuleId(engineId, channelId), false)),
        _callbackCritsect(*CriticalSectionWrapper::CreateCriticalSection()),
        _dataCritsect(*CriticalSectionWrapper::CreateCriticalSection()),
        _paused(false),
        _channelsDroppingDeltaFrames(0), _dropNextFrame(false),
        _fecEnabled(false), _nackEnabled(false), _codecObserver(NULL),
        _effectFilter(NULL), _moduleProcessThread(moduleProcessThread),
        _hasReceivedSLI(false), _pictureIdSLI(0), _hasReceivedRPSI(false),
        _pictureIdRPSI(0), _fileRecorder(channelId)
{
    WEBRTC_TRACE(webrtc::kTraceMemory,
                 webrtc::kTraceVideo,
                 ViEId(engineId, channelId),
                 "%s(engineId: %d) 0x%p - Constructor", __FUNCTION__, engineId,
                 this);

    for (int i = 0; i < kMaxSimulcastStreams; i++)
    {
        _timeLastIntraRequestMs[i] = 0;
    }
    _vcm.InitializeSender();
    _vpm.EnableTemporalDecimation(true);

     // Enable/disable content analysis: off by default for now
    _vpm.EnableContentAnalysis(false);

    _moduleProcessThread.RegisterModule(&_vcm);
    _defaultRtpRtcp.InitSender();
    _defaultRtpRtcp.RegisterIncomingVideoCallback(this);
    _defaultRtpRtcp.RegisterIncomingRTCPCallback(this);
    _moduleProcessThread.RegisterModule(&_defaultRtpRtcp);

    //
    _qmCallback = new QMTestVideoSettingsCallback();
    _qmCallback->RegisterVPM(&_vpm);
    _qmCallback->RegisterVCM(&_vcm);
    _qmCallback->SetNumOfCores(_numberOfCores);

#ifdef VIDEOCODEC_VP8
    VideoCodec videoCodec;
    if (_vcm.Codec(webrtc::kVideoCodecVP8, &videoCodec) == VCM_OK)
    {
        _vcm.RegisterSendCodec(&videoCodec, _numberOfCores,
                               _defaultRtpRtcp.MaxDataPayloadLength());
        _defaultRtpRtcp.RegisterSendPayload(videoCodec);
    }
    else
    {
        assert(false);
    }
#else
    VideoCodec videoCodec;
    if (_vcm.Codec(webrtc::kVideoCodecI420, &videoCodec) == VCM_OK)
    {
        _vcm.RegisterSendCodec(&videoCodec, _numberOfCores,
                               _defaultRtpRtcp.MaxDataPayloadLength());
        _defaultRtpRtcp.RegisterSendPayload(videoCodec);
    }
    else
    {
        assert(false);
    }
#endif

    if (_vcm.RegisterTransportCallback(this) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "%s: VCM::RegisterTransportCallback failure");
    }
    if (_vcm.RegisterSendStatisticsCallback(this) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "%s: VCM::RegisterSendStatisticsCallback failure");
    }

    if (_vcm.RegisterVideoQMCallback(_qmCallback) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "VCM::RegisterQMCallback failure");
    }
}

// ----------------------------------------------------------------------------
// Destructor
// ----------------------------------------------------------------------------

ViEEncoder::~ViEEncoder()
{
    WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                 "ViEEncoder Destructor 0x%p, engineId: %d", this, _engineId);

    if (_defaultRtpRtcp.NumberChildModules() > 0)
    {
        assert(false);
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "Channels still attached %d, leaking memory",
                   _defaultRtpRtcp.NumberChildModules());
        return;
    }
    _moduleProcessThread.DeRegisterModule(&_vcm);
    _moduleProcessThread.DeRegisterModule(&_vpm);
    _moduleProcessThread.DeRegisterModule(&_defaultRtpRtcp);
    delete &_vcm;
    delete &_vpm;
    delete &_defaultRtpRtcp;
    delete &_callbackCritsect;
    delete &_dataCritsect;
    delete _qmCallback;
}

// ============================================================================
// Start/Stop
// ============================================================================

// ----------------------------------------------------------------------------
// Pause / Retart
//
// Call this to start/stop sending
// ----------------------------------------------------------------------------

void ViEEncoder::Pause()
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                 "%s", __FUNCTION__);
    CriticalSectionScoped cs(_dataCritsect);
    _paused = true;
}

void ViEEncoder::Restart()
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                 "%s", __FUNCTION__);
    CriticalSectionScoped cs(_dataCritsect);
    _paused = false;
}

// ----------------------------------------------------------------------------
// DropDeltaAfterKey
//
// Drops the first delta frame after a key frame is encoded.
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::DropDeltaAfterKey(bool enable)
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                 "%s(%d)", __FUNCTION__, enable);
    CriticalSectionScoped cs(_dataCritsect);

    if (enable)
    {
        _channelsDroppingDeltaFrames++;
    } else
    {
        _channelsDroppingDeltaFrames--;
        if (_channelsDroppingDeltaFrames < 0)
        {
            _channelsDroppingDeltaFrames = 0;
            WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo,
                         ViEId(_engineId, _channelId),
                         "%s: Called too many times", __FUNCTION__, enable);
            return -1;
        }
    }
    return 0;
}

// ============================================================================
// Codec settigns
// ============================================================================

// ----------------------------------------------------------------------------
// NumberOfCodecs
// ----------------------------------------------------------------------------

WebRtc_UWord8 ViEEncoder::NumberOfCodecs()
{
    return _vcm.NumberOfCodecs();
}

// ----------------------------------------------------------------------------
// GetCodec
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::GetCodec(WebRtc_UWord8 listIndex,
                                   webrtc::VideoCodec& videoCodec)
{
    if (_vcm.Codec(listIndex, &videoCodec) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId), "%s: Could not get codec",
                   __FUNCTION__);
        return -1;
    }
    return 0;
}

// ----------------------------------------------------------------------------
// External encoder
// ----------------------------------------------------------------------------
WebRtc_Word32 ViEEncoder::RegisterExternalEncoder(webrtc::VideoEncoder* encoder,
                                                  WebRtc_UWord8 plType)
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                  "%s: pltype %u", __FUNCTION__, plType);

    if (encoder == NULL)
        return -1;

    if (_vcm.RegisterExternalEncoder(encoder, plType) != VCM_OK)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "Could not register external encoder");
        return -1;
    }
    return 0;
}

WebRtc_Word32 ViEEncoder::DeRegisterExternalEncoder(WebRtc_UWord8 plType)
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                 "%s: pltype %u", __FUNCTION__, plType);

    webrtc::VideoCodec currentSendCodec;
    if (_vcm.SendCodec(&currentSendCodec) == VCM_OK)
    {
        currentSendCodec.startBitrate = _vcm.Bitrate();
    }

    if (_vcm.RegisterExternalEncoder(NULL, plType) != VCM_OK)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "Could not deregister external encoder");
        return -1;
    }

    // If the external encoder is the current send codec use vcm internal encoder
    if (currentSendCodec.plType == plType)
    {
        WebRtc_UWord16 maxDataPayloadLength = _defaultRtpRtcp.MaxDataPayloadLength();
        if (_vcm.RegisterSendCodec(&currentSendCodec, _numberOfCores,
                                   maxDataPayloadLength) != VCM_OK)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                         ViEId(_engineId, _channelId),
                         "Could not use internal encoder");
            return -1;
        }
    }
    return 0;
}

// ----------------------------------------------------------------------------
// SetEncoder
// ----------------------------------------------------------------------------


WebRtc_Word32 ViEEncoder::SetEncoder(const webrtc::VideoCodec& videoCodec)
{
    WEBRTC_TRACE(webrtc::kTraceInfo,
                 webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                 "%s: CodecType: %d, width: %u, height: %u",
                 __FUNCTION__,
                 videoCodec.codecType,
                 videoCodec.width,
                 videoCodec.height);

    // Multiply startBitrate by 1000 because RTP module changed in API.
    if (_defaultRtpRtcp.SetSendBitrate(videoCodec.startBitrate * 1000,
                                       videoCodec.minBitrate,
                                       videoCodec.maxBitrate) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError,
                     webrtc::kTraceVideo,
                     ViEId(_engineId, _channelId),
                     "Could not set RTP module bitrates");
        return -1;
    }
    // Setting target width and height for VPM
    if (_vpm.SetTargetResolution(videoCodec.width,
                                 videoCodec.height,
                                 videoCodec.maxFramerate) != VPM_OK)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                 "Could not set VPM target dimensions");
        return -1;

    }

    if (_defaultRtpRtcp.RegisterSendPayload(videoCodec) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "Could register RTP module video payload");
        return -1;
    }

    WebRtc_UWord16 maxDataPayloadLength =
        _defaultRtpRtcp.MaxDataPayloadLength();

    // update QM with MaxDataPayloadLength
    _qmCallback->SetMaxPayloadLength(maxDataPayloadLength);

    if (_vcm.RegisterSendCodec(&videoCodec, _numberOfCores,
                               maxDataPayloadLength) != VCM_OK)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "Could not register send codec");
        return -1;
    }
    _dataCritsect.Enter();
    memcpy(&_sendCodec, &videoCodec, sizeof(_sendCodec)); // Copy current send codec
    _dataCritsect.Leave();

    // Set this module as sending right away, let the
    // slave module in the channel start and stop sending...
    if (_defaultRtpRtcp.Sending() == false)
    {
        if (_defaultRtpRtcp.SetSendingStatus(true) != 0)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId,
                                                             _channelId),
                       "Could start RTP module sending");
            return -1;
        }
    }
    return 0;
}

// ----------------------------------------------------------------------------
// GetSendCodec
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::GetEncoder(webrtc::VideoCodec& videoCodec)
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
               "%s", __FUNCTION__);

    if (_vcm.SendCodec(&videoCodec) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId), "Could not get VCM send codec");
        return -1;
    }
    return 0;
}

// ----------------------------------------------------------------------------
// GetCodecConfigParameters
//
// Only valid for H.264 and MPEG-4
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::GetCodecConfigParameters(
                                                   unsigned char configParameters[kConfigParameterSize],
                                                   unsigned char& configParametersSize)
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
               "%s", __FUNCTION__);

    WebRtc_Word32 numParameters =
        _vcm.CodecConfigParameters(configParameters, kConfigParameterSize);
    if (numParameters <= 0)
    {
        configParametersSize = 0;
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "Could not get config parameters");
        return -1;
    }
    configParametersSize = (unsigned char) numParameters;
    return 0;
}

// ----------------------------------------------------------------------------
// ScaleInputImage
//
// The input image will be scaled if the codec resolution differs from the
// image resolution of the input image, otherwise will the image be
// cropped/padded. Default: crop/pad.
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::ScaleInputImage(bool enable)
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
               "%s(enable %d)", __FUNCTION__, enable);

    VideoFrameResampling resamplingMode = kFastRescaling;
    if (enable == true)
    {
        // Currently not supported.
        //resamplingMode = kInterpolation;
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                     ViEId(_engineId, _channelId), "%s not supported",
                     __FUNCTION__, enable);
        return -1;
    }
    _vpm.SetInputFrameResampleMode(resamplingMode);

    return 0;
}

//=============================================================================
// RTP settings
//=============================================================================

// ----------------------------------------------------------------------------
// GetRtpRtcpModule
// ----------------------------------------------------------------------------

RtpRtcp* ViEEncoder::SendRtpRtcpModule()
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
               "%s", __FUNCTION__);

    return &_defaultRtpRtcp;
}

//=============================================================================
// Data flow
//=============================================================================

// ----------------------------------------------------------------------------
// DeliverFrame
// Implements ViEFrameCallback::DeliverFrame
// Receive videoFrame to be encoded from a provider (capture or file)
// ----------------------------------------------------------------------------

void ViEEncoder::DeliverFrame(int id, webrtc::VideoFrame& videoFrame,
                              int numCSRCs,
                              const WebRtc_UWord32 CSRC[kRtpCsrcSize])
{
    WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
               "%s: %llu", __FUNCTION__, videoFrame.TimeStamp());

    {
        CriticalSectionScoped cs(_dataCritsect);
        if (_paused || _defaultRtpRtcp.SendingMedia() == false)
        {
            // We've passed or we have no channels attached, don't encode
            return;
        }
        if (_dropNextFrame)
        {
            // Drop this frame
            WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId,
                                                              _channelId),
                       "%s: Dropping frame %llu after a key fame",
                       __FUNCTION__, videoFrame.TimeStamp());
            _dropNextFrame = false;
            return;
        }
    }
    // Set the frame timestamp
    const WebRtc_UWord32 timeStamp = 90 * (WebRtc_UWord32) videoFrame.RenderTimeMs();
    videoFrame.SetTimeStamp(timeStamp);
    {
        // Send to effect filter, if registered by user.
        CriticalSectionScoped cs(_callbackCritsect);
        if (_effectFilter)
        {
            _effectFilter->Transform(videoFrame.Length(), videoFrame.Buffer(),
                                     videoFrame.TimeStamp(),
                                     videoFrame.Width(), videoFrame.Height());
        }
    }
    // Record un-encoded frame.
    _fileRecorder.RecordVideoFrame(videoFrame);
    // Make sure the CSRC list is correct.
    if (numCSRCs > 0)
    {
        WebRtc_UWord32 tempCSRC[kRtpCsrcSize];
        for (int i = 0; i < numCSRCs; i++)
        {
            if (CSRC[i] == 1)
            {
                tempCSRC[i] = _defaultRtpRtcp.SSRC();
            }
            else
            {
                tempCSRC[i] = CSRC[i];
            }
        }
        _defaultRtpRtcp.SetCSRCs(tempCSRC, (WebRtc_UWord8) numCSRCs);
    }

#ifdef VIDEOCODEC_VP8
    if (_vcm.SendCodec() == webrtc::kVideoCodecVP8)
    {
        webrtc::CodecSpecificInfo codecSpecificInfo;
        codecSpecificInfo.codecType = webrtc::kVideoCodecVP8;
        if (_hasReceivedSLI || _hasReceivedRPSI)
        {
          {
            codecSpecificInfo.codecSpecific.VP8.hasReceivedRPSI =
                _hasReceivedRPSI;
            codecSpecificInfo.codecSpecific.VP8.hasReceivedSLI =
                _hasReceivedSLI;
            codecSpecificInfo.codecSpecific.VP8.pictureIdRPSI =
                _pictureIdRPSI;
            codecSpecificInfo.codecSpecific.VP8.pictureIdSLI  =
                _pictureIdSLI;
            }
          _hasReceivedSLI = false;
          _hasReceivedRPSI = false;
        }
        VideoFrame *decimatedFrame = NULL;
        const int ret = _vpm.PreprocessFrame(&videoFrame, &decimatedFrame);
        if (ret == 1)
        {
            // Drop this frame
            return;
        }
        else if (ret != VPM_OK)
        {
            WEBRTC_TRACE(webrtc::kTraceError,
                         webrtc::kTraceVideo,
                         ViEId(_engineId, _channelId),
                         "%s: Error preprocessing frame %u", __FUNCTION__,
                         videoFrame.TimeStamp());
            return;
        }

        VideoContentMetrics* contentMetrics = NULL;
        contentMetrics = _vpm.ContentMetrics();

        // frame was not sampled => use original
        if (decimatedFrame == NULL)  {
          decimatedFrame = &videoFrame;
        }

        if (_vcm.AddVideoFrame
            (*decimatedFrame, contentMetrics, &codecSpecificInfo) != VCM_OK)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
                       "%s: Error encoding frame %u", __FUNCTION__,
                       videoFrame.TimeStamp());
        }
        return;
    }
#endif
    // Pass frame via preprocessor
    VideoFrame *decimatedFrame = NULL;
    const int ret = _vpm.PreprocessFrame(&videoFrame, &decimatedFrame);
    if (ret == 1)
    {
        // Drop this frame
        return;
    }
    else if (ret != VPM_OK)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
                  "%s: Error preprocessing frame %u", __FUNCTION__, videoFrame.TimeStamp());
        return;
    }
    // frame was not sampled => use original
    if (decimatedFrame == NULL)  {
      decimatedFrame = &videoFrame;
    }
    if (_vcm.AddVideoFrame(*decimatedFrame) != VCM_OK)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId), "%s: Error encoding frame %u",
                   __FUNCTION__, videoFrame.TimeStamp());
    }
}
// ----------------------------------------------------------------------------
// DeliverFrame
// Implements ViEFrameCallback::DelayChanged
// ----------------------------------------------------------------------------
void ViEEncoder::DelayChanged(int id, int frameDelay)

{
    WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
               "%s: %u", __FUNCTION__, frameDelay);

    _defaultRtpRtcp.SetCameraDelay(frameDelay);
    _fileRecorder.SetFrameDelay(frameDelay);
}
// ----------------------------------------------------------------------------
// GetPreferedFrameSettings
// Implements ViEFrameCallback::GetPreferedFrameSettings
// Fetch the widh, height and frame rate prefered by this encoder.
// ----------------------------------------------------------------------------

int ViEEncoder::GetPreferedFrameSettings(int &width, int &height,
                                         int &frameRate)
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
               "%s", __FUNCTION__);

    webrtc::VideoCodec videoCodec;
    memset(&videoCodec, 0, sizeof(videoCodec));
    if (_vcm.SendCodec(&videoCodec) != VCM_OK)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId), "Could not get VCM send codec");
        return -1;
    }

    width = videoCodec.width;
    height = videoCodec.height;
    frameRate = videoCodec.maxFramerate;
    return 0;

}
// ----------------------------------------------------------------------------
// SendKeyFrame
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::SendKeyFrame()
{
    WEBRTC_TRACE(webrtc::kTraceInfo,
                 webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                 "%s", __FUNCTION__);

    return _vcm.FrameTypeRequest(kVideoFrameKey, 0); // Simulcast idx = 0
}

// ----------------------------------------------------------------------------
// SendCodecStatistics
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::SendCodecStatistics(WebRtc_UWord32& numKeyFrames,
                                              WebRtc_UWord32& numDeltaFrames)
{
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
               "%s", __FUNCTION__);

    webrtc::VCMFrameCount sentFrames;
    if (_vcm.SentFrameCount(sentFrames) != VCM_OK)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "%s: Could not get sent frame information", __FUNCTION__);
        return -1;
    }
    numKeyFrames = sentFrames.numKeyFrames;
    numDeltaFrames = sentFrames.numDeltaFrames;
    return 0;

}

//=============================================================================
// Loss protection
//=============================================================================

// ----------------------------------------------------------------------------
// UpdateProtectionMethod
//
// Updated protection method to VCM to get correct packetization sizes
// FEC has larger overhead than NACK -> set FEC if used
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::UpdateProtectionMethod()
{
    // Get FEC status
    bool fecEnabled = false;
    WebRtc_UWord8 dummyPTypeRed = 0;
    WebRtc_UWord8 dummyPTypeFEC = 0;

    // check if fec is enabled
    WebRtc_Word32 error = _defaultRtpRtcp.GenericFECStatus(fecEnabled, dummyPTypeRed,
                                                    dummyPTypeFEC);
    if (error)
    {
        return -1;
    }

    // check if nack is enabled
    bool nackEnabled = (_defaultRtpRtcp.NACK() == kNackOff) ? false : true;
    if (_fecEnabled == fecEnabled && _nackEnabled == nackEnabled)
    {
        // no change to current state
        return 0;
    }
    _fecEnabled = fecEnabled;
    _nackEnabled = nackEnabled;

    // Set Video Protection for VCM
    if (fecEnabled && nackEnabled)
    {
        _vcm.SetVideoProtection(webrtc::kProtectionNackFEC, true);
    }
    else
    {
        _vcm.SetVideoProtection(webrtc::kProtectionFEC, _fecEnabled);
        _vcm.SetVideoProtection(webrtc::kProtectionNack, _nackEnabled);
        _vcm.SetVideoProtection(webrtc::kProtectionNackFEC, false);
    }

    // If nack and/or fec is enalbed, the following should be triggered
    if (fecEnabled || nackEnabled)
    {
        WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo,
                     ViEId(_engineId, _channelId),
                     "%s: FEC status ", __FUNCTION__, fecEnabled);
        _vcm.RegisterProtectionCallback(this);
        // Need to reregister the send codec in order to set the new MTU
        webrtc::VideoCodec codec;
        if (_vcm.SendCodec(&codec) == 0)
        {
            WebRtc_UWord16 maxPayLoad = _defaultRtpRtcp.MaxDataPayloadLength();
            codec.startBitrate = _vcm.Bitrate();
            if (_vcm.RegisterSendCodec(&codec, _numberOfCores, maxPayLoad) != 0)
            {
                WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                             ViEId(_engineId, _channelId),
                             "%s: Failed to update Sendcodec when enabling FEC",
                             __FUNCTION__, fecEnabled);
                return -1;
            }
        }
        return 0;
    }
    else
    {
        // FEC and NACK are disabled
        _vcm.RegisterProtectionCallback(NULL);
    }
    return 0;
}

//=============================================================================
// Implementation of VideoPacketizationCallback from VCM
//=============================================================================

// ----------------------------------------------------------------------------
// SendData
// ----------------------------------------------------------------------------
WebRtc_Word32
ViEEncoder::SendData(const FrameType frameType,
                     const WebRtc_UWord8 payloadType,
                     const WebRtc_UWord32 timeStamp,
                     const WebRtc_UWord8* payloadData,
                     const WebRtc_UWord32 payloadSize,
                     const webrtc::RTPFragmentationHeader& fragmentationHeader,
                     const RTPVideoHeader* rtpVideoHdr)
{
    {
        CriticalSectionScoped cs(_dataCritsect);
        if (_paused)
        {
            // Paused, don't send this packet
            return 0;
        }
        if (_channelsDroppingDeltaFrames && frameType == webrtc::kVideoFrameKey)
        {
            WEBRTC_TRACE(webrtc::kTraceStream,
                         webrtc::kTraceVideo,
                         ViEId(_engineId, _channelId),
                         "%s: Sending key frame, drop next frame",
                         __FUNCTION__);
            _dropNextFrame = true;
        }
    }
    // New encoded data, hand over to the rtp module
    return _defaultRtpRtcp.SendOutgoingData(frameType,
                                            payloadType,
                                            timeStamp,
                                            payloadData,
                                            payloadSize,
                                            &fragmentationHeader,
                                            rtpVideoHdr);
}

//=============================================================================
// Implementation of VideoProtectionCallback from VCM
//=============================================================================

// ----------------------------------------------------------------------------
// ProtectionRequest
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::ProtectionRequest(const WebRtc_UWord8 deltaFECRate,
                                            const WebRtc_UWord8 keyFECRate,
                                            const bool deltaUseUepProtection,
                                            const bool keyUseUepProtection,
                                            const bool nack)
{
    WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),"%s, "
                 "deltaFECRate: %u, keyFECRate: %u, deltaUseUepProtection: "
                 "%d, keyUseUepProtection: %d, nack: %d", __FUNCTION__,
                 deltaFECRate, keyFECRate, deltaUseUepProtection,
                 keyUseUepProtection, nack);

    if (_defaultRtpRtcp.SetFECCodeRate(keyFECRate, deltaFECRate) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_engineId, _channelId),
                   "%s: Could not update FEC code rate", __FUNCTION__);
    }
    if (_defaultRtpRtcp.SetFECUepProtection(keyUseUepProtection,
                                     deltaUseUepProtection) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                     ViEId(_engineId, _channelId),
                     "%s: Could not update FEC-UEP protection", __FUNCTION__);
    }
    return 0;
}

//=============================================================================
// Implementation of VideoSendStatisticsCallback from VCM
//=============================================================================

// ----------------------------------------------------------------------------
// SendStatistics
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::SendStatistics(const WebRtc_UWord32 bitRate,
                                         const WebRtc_UWord32 frameRate)
{
    CriticalSectionScoped cs(_callbackCritsect);
    if (_codecObserver)
    {
        WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
                   "%s: bitrate %u, framerate %u", __FUNCTION__, bitRate,
                   frameRate);
        _codecObserver->OutgoingRate(_channelId, frameRate, bitRate);
    }
    return 0;
}

// ----------------------------------------------------------------------------
// RegisterCodecObserver
// ----------------------------------------------------------------------------

WebRtc_Word32 ViEEncoder::RegisterCodecObserver(ViEEncoderObserver* observer)
{
    CriticalSectionScoped cs(_callbackCritsect);
    if (observer)
    {
        WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
                   "%s: observer added", __FUNCTION__);
        if (_codecObserver)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId,
                                                             _channelId),
                       "%s: observer already set.", __FUNCTION__);
            return -1;
        }
        _codecObserver = observer;
    } else
    {
        if (_codecObserver == NULL)
        {
            WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId,
                                                            _channelId),
                       "%s: observer does not exist.", __FUNCTION__);
            return -1;
        }
        WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _channelId),
                   "%s: observer removed", __FUNCTION__);
        _codecObserver = NULL;
    }
    return 0;
}

//=============================================================================
// Implementation of RtcpFeedback
//=============================================================================

void ViEEncoder::OnSLIReceived(const WebRtc_Word32 id,
                               const WebRtc_UWord8 pictureId)
{
    _pictureIdSLI = pictureId;
    _hasReceivedSLI = true;
}

void ViEEncoder::OnRPSIReceived(const WebRtc_Word32 id,
                                const WebRtc_UWord64 pictureId)
{
    _pictureIdRPSI = pictureId;
    _hasReceivedRPSI = true;
}

//=============================================================================
// Implementation of RtpVideoFeedback
//=============================================================================

// ----------------------------------------------------------------------------
// OnReceivedIntraFrameRequest
// ----------------------------------------------------------------------------

void ViEEncoder::OnReceivedIntraFrameRequest(const WebRtc_Word32 id,
                                             const FrameType type,
                                             const WebRtc_UWord8 streamIdx)
{
    assert(streamIdx < kMaxSimulcastStreams);

    // Key frame request from other side, signal to VCM
    WEBRTC_TRACE(webrtc::kTraceStateInfo,
                 webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                 "%s", __FUNCTION__);

    WebRtc_Word64 now = TickTime::MillisecondTimestamp();
    if (_timeLastIntraRequestMs[streamIdx] + kViEMinKeyRequestIntervalMs > now)
    {
        WEBRTC_TRACE(webrtc::kTraceStream,
                     webrtc::kTraceVideo,
                     ViEId(_engineId, _channelId),
                     "%s: Not not encoding new intra due to timing",
                     __FUNCTION__);
        return;
    }
    _vcm.FrameTypeRequest(type, streamIdx);
    _timeLastIntraRequestMs[streamIdx] = now;
}

// ----------------------------------------------------------------------------
// OnNetworkChanged
// ----------------------------------------------------------------------------
void ViEEncoder::OnNetworkChanged(const WebRtc_Word32 id,
                                  const WebRtc_UWord32 bitrateBps,
                                  const WebRtc_UWord8 fractionLost,
                                  const WebRtc_UWord16 roundTripTimeMs)
{
    WEBRTC_TRACE(webrtc::kTraceInfo,
                 webrtc::kTraceVideo,
                 ViEId(_engineId, _channelId),
                 "%s(bitrateBps: %u, fractionLost: %u, rttMs: %u",
                 __FUNCTION__, bitrateBps, fractionLost, roundTripTimeMs);

    _vcm.SetChannelParameters(bitrateBps / 1000,
                              fractionLost,
                              roundTripTimeMs);
}

WebRtc_Word32 ViEEncoder::RegisterEffectFilter(ViEEffectFilter* effectFilter)
{
    CriticalSectionScoped cs(_callbackCritsect);
    if (effectFilter == NULL)
    {
        if (_effectFilter == NULL)
        {
            WEBRTC_TRACE(webrtc::kTraceError,
                         webrtc::kTraceVideo,
                         ViEId(_engineId,_channelId),
                         "%s: no effect filter added",
                         __FUNCTION__);
            return -1;
        }
        WEBRTC_TRACE(webrtc::kTraceInfo,
                     webrtc::kTraceVideo,
                     ViEId(_engineId, _channelId),
                     "%s: deregister effect filter",
                     __FUNCTION__);
    } else
    {
        WEBRTC_TRACE(webrtc::kTraceInfo,
                     webrtc::kTraceVideo,
                     ViEId(_engineId, _channelId),
                     "%s: register effect",
                     __FUNCTION__);
        if (_effectFilter)
        {
            WEBRTC_TRACE(webrtc::kTraceError,
                         webrtc::kTraceVideo,
                         ViEId(_engineId,_channelId),
                         "%s: effect filter already added ",
                         __FUNCTION__);
            return -1;
        }
    }
    _effectFilter = effectFilter;
    return 0;
}

ViEFileRecorder& ViEEncoder::GetOutgoingFileRecorder()
{
    return _fileRecorder;
}

//=============================================================================
// Implementation of Video QM settings callback:
// Callback to be called from VCM to update VPM of frame rate and size
//=============================================================================

ViEEncoder::QMTestVideoSettingsCallback::QMTestVideoSettingsCallback():
_vpm(NULL),
_vcm(NULL)
{

}

void ViEEncoder::QMTestVideoSettingsCallback::
                 RegisterVPM(VideoProcessingModule *vpm)
{
    _vpm = vpm;
}

void ViEEncoder::QMTestVideoSettingsCallback::
                 RegisterVCM(VideoCodingModule *vcm)
{
    _vcm = vcm;
}

WebRtc_Word32 ViEEncoder::QMTestVideoSettingsCallback::SetVideoQMSettings
                              (const WebRtc_UWord32 frameRate,
                               const WebRtc_UWord32 width,
                               const WebRtc_UWord32 height)
{

    WebRtc_Word32 retVal = 0;
    retVal = _vpm->SetTargetResolution(width, height, frameRate);
    //Initialize codec with new values
    if (!retVal)
    {
        // first get current settings
        VideoCodec currentCodec;
        _vcm->SendCodec(&currentCodec);

        WebRtc_UWord32 currentBitRate = _vcm->Bitrate();

        // now set new values:
        currentCodec.height = (WebRtc_UWord16)height;
        currentCodec.width = (WebRtc_UWord16)width;
        currentCodec.maxFramerate = (WebRtc_UWord8)frameRate;
        currentCodec.startBitrate = currentBitRate;

        // re-register encoder
        retVal = _vcm->RegisterSendCodec(&currentCodec,_numOfCores,_maxPayloadLength);
    }

    return retVal;
}

/////////////////////


} // namespace webrtc
