/*
 *  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.
 */

/*
 * vie_codec_impl.cc
 */

#include "vie_codec_impl.h"

// Defines
#include "engine_configurations.h"
#include "vie_defines.h"

#include "video_coding.h"
#include "trace.h"
#include "vie_errors.h"
#include "vie_impl.h"
#include "vie_channel.h"
#include "vie_channel_manager.h"
#include "vie_encoder.h"
#include "vie_input_manager.h"
#include "vie_capturer.h"

#include <string.h>

namespace webrtc
{

// ----------------------------------------------------------------------------
// GetInterface
// ----------------------------------------------------------------------------

ViECodec* ViECodec::GetInterface(VideoEngine* videoEngine)
{
#ifdef WEBRTC_VIDEO_ENGINE_CODEC_API
    if (videoEngine == NULL)
    {
        return NULL;
    }
    VideoEngineImpl* vieImpl = reinterpret_cast<VideoEngineImpl*> (videoEngine);
    ViECodecImpl* vieCodecImpl = vieImpl;
    (*vieCodecImpl)++; // Increase ref count

    return vieCodecImpl;
#else
    return NULL;
#endif
}

// ----------------------------------------------------------------------------
// Release
//
// Releases the interface, i.e. reduces the reference counter. The number of
// remaining references is returned, -1 if released too many times.
// ----------------------------------------------------------------------------

int ViECodecImpl::Release()
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, _instanceId,
               "ViECodecImpl::Release()");
    (*this)--; // Decrease ref count

    WebRtc_Word32 refCount = GetCount();
    if (refCount < 0)
    {
        WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, _instanceId,
                   "ViECodec released too many times");
        SetLastError(kViEAPIDoesNotExist);
        return -1;
    }
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, _instanceId,
               "ViECodec reference count: %d", refCount);
    return refCount;
}

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

ViECodecImpl::ViECodecImpl()
{
    WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId,
               "ViECodecImpl::ViECodecImpl() Ctor");
}

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

ViECodecImpl::~ViECodecImpl()
{
    WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideo, _instanceId,
               "ViECodecImpl::~ViECodecImpl() Dtor");
}

// Available codecs
// ----------------------------------------------------------------------------
// NumberOfCodecs
//
// Returns the number of available codecs
// ----------------------------------------------------------------------------

int ViECodecImpl::NumberOfCodecs() const
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s",
               __FUNCTION__);
    // +2 because of FEC(RED and ULPFEC)
    return (int) (VideoCodingModule::NumberOfCodecs() + 2);
}

// ----------------------------------------------------------------------------
// GetCodec
//
// Return the video codec with listNumber
// ----------------------------------------------------------------------------

int ViECodecImpl::GetCodec(const unsigned char listNumber,
                           VideoCodec& videoCodec) const
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId),
               "%s(listNumber: %d, codecType: %d)", __FUNCTION__,
               listNumber, videoCodec.codecType);
    if (listNumber == VideoCodingModule::NumberOfCodecs())
    {
        memset(&videoCodec, 0, sizeof(webrtc::VideoCodec));
        strcpy(videoCodec.plName, "RED");
        videoCodec.codecType = kVideoCodecRED;
        videoCodec.plType = VCM_RED_PAYLOAD_TYPE;
    }
    else if (listNumber == VideoCodingModule::NumberOfCodecs() + 1)
    {
        memset(&videoCodec, 0, sizeof(webrtc::VideoCodec));
        strcpy(videoCodec.plName, "ULPFEC");
        videoCodec.codecType = kVideoCodecULPFEC;
        videoCodec.plType = VCM_ULPFEC_PAYLOAD_TYPE;
    }
    else if (VideoCodingModule::Codec(listNumber, &videoCodec)
        != VCM_OK)
    {
        WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId),
                   "%s: Could not get codec for listNumber: %u", __FUNCTION__,
                   listNumber);
        SetLastError(kViECodecInvalidArgument);
        return -1;
    }
    return 0;
}

// Codec settings
// ----------------------------------------------------------------------------
// SetSendCodec
//
// Sets the send codec for videoChannel
// This call will affect all channels using the same encoder
// ----------------------------------------------------------------------------

int ViECodecImpl::SetSendCodec(const int videoChannel,
                               const VideoCodec& videoCodec)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId,videoChannel),
               "%s(videoChannel: %d, codecType: %d)", __FUNCTION__,
               videoChannel, videoCodec.codecType);
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_instanceId, videoChannel),
               "%s: codec: %d, plType: %d, width: %d, height: %d, bitrate: %d"
               "maxBr: %d, minBr: %d, frameRate: %d)", __FUNCTION__,
               videoCodec.codecType, videoCodec.plType, videoCodec.width,
               videoCodec.height, videoCodec.startBitrate,
               videoCodec.maxBitrate, videoCodec.minBitrate,
               videoCodec.maxFramerate);

    if (CodecValid(videoCodec) == false)
    {
        // Error logged
        SetLastError(kViECodecInvalidCodec);
        return -1;
    }

    ViEChannelManagerScoped cs(_channelManager);
    ViEChannel* vieChannel = cs.Channel(videoChannel);
    if (vieChannel == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }

    // Set a maxBitrate if the user hasn't...
    VideoCodec videoCodecInternal;
    memcpy(&videoCodecInternal, &videoCodec, sizeof(webrtc::VideoCodec));
    if (videoCodecInternal.maxBitrate == 0)
    {
        // Max is one bit per pixel ...
        videoCodecInternal.maxBitrate = (videoCodecInternal.width
            * videoCodecInternal.height * videoCodecInternal.maxFramerate)
            / 1000;
        if (videoCodecInternal.startBitrate > videoCodecInternal.maxBitrate)
        {
            // ... but should'nt limit the set start bitrate.
            videoCodecInternal.maxBitrate = videoCodecInternal.startBitrate;
        }
        WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_instanceId,
                                                        videoChannel),
                   "%s: New max bitrate set to %d kbps", __FUNCTION__,
                   videoCodecInternal.maxBitrate);
    }

    ViEEncoder* vieEncoder = cs.Encoder(videoChannel);
    if (vieEncoder == NULL)
    {
        assert(false);
        WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel),
                   "%s: No encoder found for channel %d", __FUNCTION__);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }

    // We need to check if the codec settings changed,
    // then we need a new SSRC
    bool newRtpStream = false;

    VideoCodec encoder;
    vieEncoder->GetEncoder(encoder);
    if (encoder.codecType != videoCodecInternal.codecType ||
        encoder.width != videoCodecInternal.width ||
        encoder.height != videoCodecInternal.height)
    {
        if (cs.ChannelUsingViEEncoder(videoChannel))
        {
            // We don't allow changing codec type or size when several
            // channels share encoder.
            WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo,
                       ViEId(_instanceId, videoChannel),
                       "%s: Settings differs from other channels using encoder",
                       __FUNCTION__);
            SetLastError(kViECodecInUse);
            return -1;
        }
        newRtpStream = true;
    }
    if (videoCodecInternal.numberOfSimulcastStreams > 1)
    {
        if (cs.ChannelUsingViEEncoder(videoChannel))
        {
            // We don't allow simulcast channels to share encoder
            WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo,
                       ViEId(_instanceId, videoChannel),
                       "%s: Can't share simulcast encoder",
                       __FUNCTION__);
            SetLastError(kViECodecInUse);
            return -1;
        }
    }

    ViEInputManagerScoped is(_inputManager);
    ViEFrameProviderBase* frameProvider = NULL;

    // Stop the media flow while reconfiguring
    vieEncoder->Pause();

    // Check if we have a frame provider that is a camera and can provide this
    // codec for us.
    bool useCaptureDeviceAsEncoder = false;
    frameProvider = is.FrameProvider(vieEncoder);
    if (frameProvider)
    {
        ViECapturer* vieCapture = static_cast<ViECapturer *> (frameProvider);
        // Try to get preencoded. Nothing to do if it is not supported.
        if (vieCapture && vieCapture->PreEncodeToViEEncoder(videoCodecInternal,
                                                            *vieEncoder,
                                                            videoChannel) == 0)
        {
            useCaptureDeviceAsEncoder = true;
        }
    }

    // Update the encoder settings if we are not using a capture device capable
    // of this codec.
    if (!useCaptureDeviceAsEncoder
        && vieEncoder->SetEncoder(videoCodecInternal) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel),
                   "%s: Could not change encoder for channel %d", __FUNCTION__,
                   videoChannel);
        SetLastError(kViECodecUnknownError);
        return -1;
    }

    // Give the channel the new information
    if (vieChannel->SetSendCodec(videoCodecInternal, newRtpStream) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel),
                   "%s: Could not set send codec for channel %d", __FUNCTION__,
                   videoChannel);
        SetLastError(kViECodecUnknownError);
        return -1;
    }

    // Update the protection mode, we might be switching NACK/FEC
    vieEncoder->UpdateProtectionMethod();
    // Get new best format for frame provider
    if (frameProvider)
    {
        frameProvider->FrameCallbackChanged();
    }
    // Restart the media flow
    if (newRtpStream)
    {
        // Stream settings changed, make sure we get a key frame
        vieEncoder->SendKeyFrame();
    }
    vieEncoder->Restart();

    return 0;
}

// ----------------------------------------------------------------------------
// GetSendCodec
//
// Gets the current send codec
// ----------------------------------------------------------------------------

int ViECodecImpl::GetSendCodec(const int videoChannel,
                               VideoCodec& videoCodec) const
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)",
               __FUNCTION__, videoChannel);

    ViEChannelManagerScoped cs(_channelManager);
    ViEEncoder* vieEncoder = cs.Encoder(videoChannel);
    if (vieEncoder == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel),
                   "%s: No encoder for channel %d", __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }

    return vieEncoder->GetEncoder(videoCodec);
}

// ----------------------------------------------------------------------------
// SetReceiveCodec
//
// Registers a possible receive codec
// ----------------------------------------------------------------------------

int ViECodecImpl::SetReceiveCodec(const int videoChannel,
                                  const VideoCodec& videoCodec)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId, videoChannel),
               "%s(videoChannel: %d, codecType: %d)", __FUNCTION__,
               videoChannel, videoCodec.codecType);
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_instanceId, videoChannel),
               "%s: codec: %d, plType: %d, width: %d, height: %d, bitrate: %d,"
               "maxBr: %d, minBr: %d, frameRate: %d", __FUNCTION__,
               videoCodec.codecType, videoCodec.plType, videoCodec.width,
               videoCodec.height, videoCodec.startBitrate,
               videoCodec.maxBitrate, videoCodec.minBitrate,
               videoCodec.maxFramerate);

    if (CodecValid(videoCodec) == false)
    {
        // Error logged
        SetLastError(kViECodecInvalidCodec);
        return -1;
    }

    ViEChannelManagerScoped cs(_channelManager);
    ViEChannel* vieChannel = cs.Channel(videoChannel);
    if (vieChannel == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }

    if (vieChannel->SetReceiveCodec(videoCodec) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_instanceId,
                                                         videoChannel),
                   "%s: Could not set receive codec for channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecUnknownError);
        return -1;
    }

    return 0;
}

// ----------------------------------------------------------------------------
// GetReceiveCodec
//
// Gets the current receive codec
// ----------------------------------------------------------------------------

int ViECodecImpl::GetReceiveCodec(const int videoChannel,
                                  VideoCodec& videoCodec) const
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId, videoChannel),
               "%s(videoChannel: %d, codecType: %d)", __FUNCTION__,
               videoChannel, videoCodec.codecType);

    ViEChannelManagerScoped cs(_channelManager);
    ViEChannel* vieChannel = cs.Channel(videoChannel);
    if (vieChannel == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }

    if (vieChannel->GetReceiveCodec(videoCodec) != 0)
    {
        SetLastError(kViECodecUnknownError);
        return -1;
    }
    return 0;
}

// ----------------------------------------------------------------------------
// GetCodecConfigParameters
//
// Gets the codec config parameters to be sent out-of-band.
// ----------------------------------------------------------------------------

int ViECodecImpl::GetCodecConfigParameters(
    const int videoChannel,
    unsigned char configParameters[kConfigParameterSize],
    unsigned char& configParametersSize) const
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)",
               __FUNCTION__, videoChannel);

    ViEChannelManagerScoped cs(_channelManager);
    ViEEncoder* vieEncoder = cs.Encoder(videoChannel);
    if (vieEncoder == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel),
                   "%s: No encoder for channel %d", __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }

    if (vieEncoder->GetCodecConfigParameters(configParameters,
                                             configParametersSize) != 0)
    {
        SetLastError(kViECodecUnknownError);
        return -1;
    }

    return 0;
}

// ----------------------------------------------------------------------------
// SetImageScaleStatus
//
// Enables scaling of the encoded image instead of padding black border or
// cropping
// ----------------------------------------------------------------------------

int ViECodecImpl::SetImageScaleStatus(const int videoChannel, const bool enable)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId, videoChannel),
               "%s(videoChannel: %d, enable: %d)", __FUNCTION__, videoChannel,
               enable);

    ViEChannelManagerScoped cs(_channelManager);
    ViEEncoder* vieEncoder = cs.Encoder(videoChannel);
    if (vieEncoder == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }

    if (vieEncoder->ScaleInputImage(enable) != 0)
    {
        SetLastError(kViECodecUnknownError);
        return -1;
    }
    return 0;
}

// Codec statistics
// ----------------------------------------------------------------------------
// GetSendCodecStastistics
//
// Get codec statistics for outgoing stream
// ----------------------------------------------------------------------------


int ViECodecImpl::GetSendCodecStastistics(const int videoChannel,
                                          unsigned int& keyFrames,
                                          unsigned int& deltaFrames) const
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId, videoChannel), "%s(videoChannel %d)",
               __FUNCTION__, videoChannel);

    ViEChannelManagerScoped cs(_channelManager);
    ViEEncoder* vieEncoder = cs.Encoder(videoChannel);
    if (vieEncoder == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel),
                   "%s: No send codec for channel %d", __FUNCTION__,
                   videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }

    if (vieEncoder->SendCodecStatistics((WebRtc_UWord32&) keyFrames,
                                        (WebRtc_UWord32&) deltaFrames) != 0)
    {
        SetLastError(kViECodecUnknownError);
        return -1;
    }
    return 0;
}

// ----------------------------------------------------------------------------
// GetReceiveCodecStastistics
//
// Get codec statistics for incoming stream
// ----------------------------------------------------------------------------

int ViECodecImpl::GetReceiveCodecStastistics(const int videoChannel,
                                             unsigned int& keyFrames,
                                             unsigned int& deltaFrames) const
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId, videoChannel),
               "%s(videoChannel: %d, codecType: %d)", __FUNCTION__,
               videoChannel);

    ViEChannelManagerScoped cs(_channelManager);
    ViEChannel* vieChannel = cs.Channel(videoChannel);
    if (vieChannel == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }
    if (vieChannel->ReceiveCodecStatistics((WebRtc_UWord32&) keyFrames,
                                           (WebRtc_UWord32&) deltaFrames) != 0)
    {
        SetLastError(kViECodecUnknownError);
        return -1;
    }
    return 0;

}

unsigned int ViECodecImpl::GetDiscardedPackets(const int videoChannel) const {
  WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId, videoChannel),
               "%s(videoChannel: %d, codecType: %d)", __FUNCTION__,
               videoChannel);

  ViEChannelManagerScoped cs(_channelManager);
  ViEChannel* vieChannel = cs.Channel(videoChannel);
  if (vieChannel == NULL)
  {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                 ViEId(_instanceId, videoChannel), "%s: No channel %d",
                 __FUNCTION__, videoChannel);
    SetLastError(kViECodecInvalidChannelId);
    return -1;
  }
  return vieChannel->DiscardedPackets();
}

// Callbacks
// ----------------------------------------------------------------------------
// SetKeyFrameRequestCallbackStatus
//
// Enables a kecallback for keyframe request instead of using RTCP
// ----------------------------------------------------------------------------

int ViECodecImpl::SetKeyFrameRequestCallbackStatus(const int videoChannel,
                                                   const bool enable)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId, videoChannel), "%s(videoChannel: %d)",
               __FUNCTION__, videoChannel);

    ViEChannelManagerScoped cs(_channelManager);
    ViEChannel* vieChannel = cs.Channel(videoChannel);
    if (vieChannel == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }
    if (vieChannel->EnableKeyFrameRequestCallback(enable) != 0)
    {
        SetLastError(kViECodecUnknownError);
        return -1;
    }
    return 0;

}

// ----------------------------------------------------------------------------
// SetSignalKeyPacketLossStatus
//
// Triggers a key frame request when there is packet loss in a received key
// frame
// ----------------------------------------------------------------------------

int ViECodecImpl::SetSignalKeyPacketLossStatus(const int videoChannel,
                                               const bool enable,
                                               const bool onlyKeyFrames)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo,
               ViEId(_instanceId, videoChannel),
               "%s(videoChannel: %d, enable: %d, onlyKeyFrames: %d)",
               __FUNCTION__, videoChannel, enable);

    ViEChannelManagerScoped cs(_channelManager);
    ViEChannel* vieChannel = cs.Channel(videoChannel);
    if (vieChannel == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }
    if (vieChannel->SetSignalPacketLossStatus(enable, onlyKeyFrames) != 0)
    {
        SetLastError(kViECodecUnknownError);
        return -1;
    }
    return 0;
}

// ----------------------------------------------------------------------------
// RegisterEncoderObserver
// ----------------------------------------------------------------------------

int ViECodecImpl::RegisterEncoderObserver(const int videoChannel,
                                          ViEEncoderObserver& observer)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s",
               __FUNCTION__);

    ViEChannelManagerScoped cs(_channelManager);
    ViEEncoder* vieEncoder = cs.Encoder(videoChannel);
    if (vieEncoder == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel),
                   "%s: No encoder for channel %d", __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }
    if (vieEncoder->RegisterCodecObserver(&observer) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel),
                   "%s: Could not register codec observer at channel",
                   __FUNCTION__);
        SetLastError(kViECodecObserverAlreadyRegistered);
        return -1;
    }
    return 0;
}

// ----------------------------------------------------------------------------
// DeregisterEncoderObserver
// ----------------------------------------------------------------------------

int ViECodecImpl::DeregisterEncoderObserver(const int videoChannel)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s",
               __FUNCTION__);

    ViEChannelManagerScoped cs(_channelManager);
    ViEEncoder* vieEncoder = cs.Encoder(videoChannel);
    if (vieEncoder == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel),
                   "%s: No encoder for channel %d", __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }
    if (vieEncoder->RegisterCodecObserver(NULL) != 0)
    {
        SetLastError(kViECodecObserverNotRegistered);
        return -1;
    }
    return 0;
}

// ----------------------------------------------------------------------------
// RegisterDecoderObserver
// ----------------------------------------------------------------------------

int ViECodecImpl::RegisterDecoderObserver(const int videoChannel,
                                          ViEDecoderObserver& observer)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s",
               __FUNCTION__);

    ViEChannelManagerScoped cs(_channelManager);
    ViEChannel* vieChannel = cs.Channel(videoChannel);
    if (vieChannel == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }
    if (vieChannel->RegisterCodecObserver(&observer) != 0)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel),
                   "%s: Could not register codec observer at channel",
                   __FUNCTION__);
        SetLastError(kViECodecObserverAlreadyRegistered);
        return -1;
    }
    return 0;
}

// ----------------------------------------------------------------------------
// DeregisterDecoderObserver
// ----------------------------------------------------------------------------

int ViECodecImpl::DeregisterDecoderObserver(const int videoChannel)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId), "%s",
               __FUNCTION__);

    ViEChannelManagerScoped cs(_channelManager);
    ViEChannel* vieChannel = cs.Channel(videoChannel);
    if (vieChannel == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }
    if (vieChannel->RegisterCodecObserver(NULL) != 0)
    {
        SetLastError(kViECodecObserverNotRegistered);
        return -1;
    }

    return 0;
}

// Force a key frame
// ----------------------------------------------------------------------------
// SendKeyFrame
//
// Force the next frame to be a key frame
// ----------------------------------------------------------------------------

int ViECodecImpl::SendKeyFrame(const int videoChannel)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId),
               "%s(videoChannel: %d)", __FUNCTION__, videoChannel);

    ViEChannelManagerScoped cs(_channelManager);
    ViEEncoder* vieEncoder = cs.Encoder(videoChannel);
    if (vieEncoder == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }
    if (vieEncoder->SendKeyFrame() != 0)
    {
        SetLastError(kViECodecUnknownError);
        return -1;
    }
    return 0;

}

// ----------------------------------------------------------------------------
// WaitForFirstKeyFrame
//
// Forc the next frame to be a key frame
// ----------------------------------------------------------------------------

int ViECodecImpl::WaitForFirstKeyFrame(const int videoChannel, const bool wait)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId),
               "%s(videoChannel: %d, wait: %d)", __FUNCTION__, videoChannel,
               wait);

    ViEChannelManagerScoped cs(_channelManager);
    ViEChannel* vieChannel = cs.Channel(videoChannel);
    if (vieChannel == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId, videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }
    if (vieChannel->WaitForKeyFrame(wait) != 0)
    {
        SetLastError(kViECodecUnknownError);
        return -1;
    }
    return 0;
}

// H263 Specific
// ----------------------------------------------------------------------------
// SetInverseH263Logic
//
// Used to interoperate with old MS H.263 where key frames are marked as delta
// and the oposite.
// ----------------------------------------------------------------------------

int ViECodecImpl::SetInverseH263Logic(int videoChannel, bool enable)
{
    WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideo, ViEId(_instanceId),
               "%s(videoChannel: %d)", __FUNCTION__, videoChannel);

    ViEChannelManagerScoped cs(_channelManager);
    ViEChannel* vieChannel = cs.Channel(videoChannel);
    if (vieChannel == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo,
                   ViEId(_instanceId,videoChannel), "%s: No channel %d",
                   __FUNCTION__, videoChannel);
        SetLastError(kViECodecInvalidChannelId);
        return -1;
    }
    if (vieChannel->SetInverseH263Logic(enable) != 0)
    {
        SetLastError(kViECodecUnknownError);
        return -1;
    }
    return 0;
}

// ----------------------------------------------------------------------------
// CodecValid
// ----------------------------------------------------------------------------

bool ViECodecImpl::CodecValid(const VideoCodec& videoCodec)
{
    // Check plName matches codecType
    if (videoCodec.codecType == kVideoCodecRED)
    {
#if defined(WIN32)
        if (_strnicmp(videoCodec.plName, "red", 3) == 0)
#elif defined(WEBRTC_MAC_INTEL) || defined(WEBRTC_LINUX)
        if (strncasecmp(videoCodec.plName, "red",3) == 0)
#endif
        {
            // We only care about the type and name for red
            return true;
        }
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1,
                   "Codec type doesn't match plName", videoCodec.plType);
        return false;
    }
    else if (videoCodec.codecType == kVideoCodecULPFEC)
    {
#if defined(WIN32)
        if (_strnicmp(videoCodec.plName, "ULPFEC", 6) == 0)
#elif defined(WEBRTC_MAC_INTEL)|| defined(WEBRTC_LINUX)
        if (strncasecmp(videoCodec.plName, "ULPFEC",6) == 0)
#endif
        {
            // We only care about the type and name for ULPFEC
            return true;
        }
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1,
                   "Codec type doesn't match plName", videoCodec.plType);
        return false;
    }
    else if ((videoCodec.codecType == kVideoCodecH263 &&
             strncmp(videoCodec.plName, "H263", 4) == 0)
            || (videoCodec.codecType == kVideoCodecH263
                && strncmp(videoCodec.plName, "H263-1998", 9) == 0)
            || (videoCodec.codecType == kVideoCodecVP8
                && strncmp(videoCodec.plName, "VP8", 4) == 0)
            || (videoCodec.codecType == kVideoCodecI420
                && strncmp(videoCodec.plName, "I420", 4) == 0)
            || (videoCodec.codecType == kVideoCodecH264
                && strncmp(videoCodec.plName, "H264", 4) == 0))
            // || (videoCodec.codecType == kVideoCodecMPEG4
            //  && strncmp(videoCodec.plName, "MP4V-ES", 7) == 0)
    {
        // ok
    }
    else
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1,
                   "Codec type doesn't match plName", videoCodec.plType);
        return false;
    }

    // pltype
    if (videoCodec.plType == 0 && videoCodec.plType > 127)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1,
                   "Invalid codec payload type: %d", videoCodec.plType);
        return false;
    }

    // Size
    if (videoCodec.width > kViEMaxCodecWidth || videoCodec.height
        > kViEMaxCodecHeight)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1,
                   "Invalid codec size: %u x %u", videoCodec.width,
                   videoCodec.height);
        return false;
    }

    if (videoCodec.startBitrate < kViEMinCodecBitrate)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1,
                   "Invalid startBitrate: %u", videoCodec.startBitrate);
        return false;
    }
    if (videoCodec.minBitrate < kViEMinCodecBitrate)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1,
                   "Invalid minBitrate: %u", videoCodec.minBitrate);
        return false;
    }
    if (videoCodec.numberOfSimulcastStreams == 1)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1,
                     "Number of Simulcast streams can not be 1");
        return false;
    }
    if (videoCodec.codecType == kVideoCodecH263)
    {
        if ((videoCodec.width == 704 && videoCodec.height == 576)
            || (videoCodec.width == 352 && videoCodec.height == 288)
            || (videoCodec.width == 176 && videoCodec.height == 144)
            || (videoCodec.width == 128 && videoCodec.height == 96))
        {
            // ok
        }
        else
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, -1,
                       "Invalid size for H.263");
            return false;
        }
    }
    return true;
}
} // namespace webrtc
