/*
 *  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 "webrtc/modules/audio_coding/main/source/acm_neteq.h"

#include <stdlib.h>  // malloc

#include <algorithm>  // sort
#include <vector>

#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
#include "webrtc/common_types.h"
#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h"
#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h"
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/system_wrappers/interface/trace_event.h"

namespace webrtc {

namespace acm1 {

#define RTP_HEADER_SIZE 12
#define NETEQ_INIT_FREQ 8000
#define NETEQ_INIT_FREQ_KHZ (NETEQ_INIT_FREQ/1000)
#define NETEQ_ERR_MSG_LEN_BYTE (WEBRTC_NETEQ_MAX_ERROR_NAME + 1)

ACMNetEQ::ACMNetEQ()
    : id_(0),
      current_samp_freq_khz_(NETEQ_INIT_FREQ_KHZ),
      avt_playout_(false),
      playout_mode_(voice),
      neteq_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
      vad_status_(false),
      vad_mode_(VADNormal),
      decode_lock_(RWLockWrapper::CreateRWLock()),
      num_slaves_(0),
      received_stereo_(false),
      master_slave_info_(NULL),
      previous_audio_activity_(AudioFrame::kVadUnknown),
      callback_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
      min_of_max_num_packets_(0),
      min_of_buffer_size_bytes_(0),
      per_packet_overhead_bytes_(0),
      av_sync_(false),
      minimum_delay_ms_(0),
      maximum_delay_ms_(0) {
  for (int n = 0; n < MAX_NUM_SLAVE_NETEQ + 1; n++) {
    is_initialized_[n] = false;
    ptr_vadinst_[n] = NULL;
    inst_[n] = NULL;
    inst_mem_[n] = NULL;
    neteq_packet_buffer_[n] = NULL;
  }
}

ACMNetEQ::~ACMNetEQ() {
  {
    CriticalSectionScoped lock(neteq_crit_sect_);
    RemoveNetEQSafe(0);  // Master.
    RemoveSlavesSafe();
  }
  if (neteq_crit_sect_ != NULL) {
    delete neteq_crit_sect_;
  }

  if (decode_lock_ != NULL) {
    delete decode_lock_;
  }

  if (callback_crit_sect_ != NULL) {
    delete callback_crit_sect_;
  }
}

int32_t ACMNetEQ::Init() {
  CriticalSectionScoped lock(neteq_crit_sect_);

  for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) {
    if (InitByIdxSafe(idx) < 0) {
      return -1;
    }
    // delete VAD instance and start fresh if required.
    if (ptr_vadinst_[idx] != NULL) {
      WebRtcVad_Free(ptr_vadinst_[idx]);
      ptr_vadinst_[idx] = NULL;
    }
    if (vad_status_) {
      // Has to enable VAD
      if (EnableVADByIdxSafe(idx) < 0) {
        // Failed to enable VAD.
        // Delete VAD instance, if it is created
        if (ptr_vadinst_[idx] != NULL) {
          WebRtcVad_Free(ptr_vadinst_[idx]);
          ptr_vadinst_[idx] = NULL;
        }
        // We are at initialization of NetEq, if failed to
        // enable VAD, we delete the NetEq instance.
        if (inst_mem_[idx] != NULL) {
          free(inst_mem_[idx]);
          inst_mem_[idx] = NULL;
          inst_[idx] = NULL;
        }
        is_initialized_[idx] = false;
        return -1;
      }
    }
    is_initialized_[idx] = true;
  }
  if (EnableVAD() == -1) {
    return -1;
  }
  return 0;
}

int16_t ACMNetEQ::InitByIdxSafe(const int16_t idx) {
  int memory_size_bytes;
  if (WebRtcNetEQ_AssignSize(&memory_size_bytes) != 0) {
    LogError("AssignSize", idx);
    return -1;
  }

  if (inst_mem_[idx] != NULL) {
    free(inst_mem_[idx]);
    inst_mem_[idx] = NULL;
    inst_[idx] = NULL;
  }
  inst_mem_[idx] = malloc(memory_size_bytes);
  if (inst_mem_[idx] == NULL) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "InitByIdxSafe: NetEq Initialization error: could not "
                 "allocate memory for NetEq");
    is_initialized_[idx] = false;
    return -1;
  }
  if (WebRtcNetEQ_Assign(&inst_[idx], inst_mem_[idx]) != 0) {
    if (inst_mem_[idx] != NULL) {
      free(inst_mem_[idx]);
      inst_mem_[idx] = NULL;
      inst_[idx] = NULL;
    }
    LogError("Assign", idx);
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "InitByIdxSafe: NetEq Initialization error: could not Assign");
    is_initialized_[idx] = false;
    return -1;
  }
  if (WebRtcNetEQ_Init(inst_[idx], NETEQ_INIT_FREQ) != 0) {
    if (inst_mem_[idx] != NULL) {
      free(inst_mem_[idx]);
      inst_mem_[idx] = NULL;
      inst_[idx] = NULL;
    }
    LogError("Init", idx);
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "InitByIdxSafe: NetEq Initialization error: could not "
                 "initialize NetEq");
    is_initialized_[idx] = false;
    return -1;
  }
  is_initialized_[idx] = true;
  return 0;
}

int16_t ACMNetEQ::EnableVADByIdxSafe(const int16_t idx) {
  if (ptr_vadinst_[idx] == NULL) {
    if (WebRtcVad_Create(&ptr_vadinst_[idx]) < 0) {
      ptr_vadinst_[idx] = NULL;
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "EnableVADByIdxSafe: NetEq Initialization error: could not "
                   "create VAD");
      return -1;
    }
  }

  if (WebRtcNetEQ_SetVADInstance(
      inst_[idx], ptr_vadinst_[idx],
      (WebRtcNetEQ_VADInitFunction) WebRtcVad_Init,
      (WebRtcNetEQ_VADSetmodeFunction) WebRtcVad_set_mode,
      (WebRtcNetEQ_VADFunction) WebRtcVad_Process) < 0) {
    LogError("setVADinstance", idx);
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "EnableVADByIdxSafe: NetEq Initialization error: could not "
                 "set VAD instance");
    return -1;
  }

  if (WebRtcNetEQ_SetVADMode(inst_[idx], vad_mode_) < 0) {
    LogError("setVADmode", idx);
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "EnableVADByIdxSafe: NetEq Initialization error: could not "
                 "set VAD mode");
    return -1;
  }
  return 0;
}

int32_t ACMNetEQ::AllocatePacketBuffer(
    const WebRtcNetEQDecoder* used_codecs,
    int16_t num_codecs) {
  // Due to WebRtcNetEQ_GetRecommendedBufferSize
  // the following has to be int otherwise we will have compiler error
  // if not casted

  CriticalSectionScoped lock(neteq_crit_sect_);
  for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) {
    if (AllocatePacketBufferByIdxSafe(used_codecs, num_codecs, idx) < 0) {
      return -1;
    }
  }
  return 0;
}

int16_t ACMNetEQ::AllocatePacketBufferByIdxSafe(
    const WebRtcNetEQDecoder* used_codecs,
    int16_t num_codecs,
    const int16_t idx) {
  int max_num_packets;
  int buffer_size_in_bytes;
  int per_packet_overhead_bytes;

  if (!is_initialized_[idx]) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "AllocatePacketBufferByIdxSafe: NetEq is not initialized.");
    return -1;
  }
  if (WebRtcNetEQ_GetRecommendedBufferSize(inst_[idx], used_codecs,
                                           num_codecs,
                                           kTCPXLargeJitter,
                                           &max_num_packets,
                                           &buffer_size_in_bytes,
                                           &per_packet_overhead_bytes) != 0) {
    LogError("GetRecommendedBufferSize", idx);
    return -1;
  }
  if (idx == 0) {
    min_of_buffer_size_bytes_ = buffer_size_in_bytes;
    min_of_max_num_packets_ = max_num_packets;
    per_packet_overhead_bytes_ = per_packet_overhead_bytes;
  } else {
    min_of_buffer_size_bytes_ = std::min(min_of_buffer_size_bytes_,
                                        buffer_size_in_bytes);
    min_of_max_num_packets_ = std::min(min_of_max_num_packets_,
                                       max_num_packets);
  }
  if (neteq_packet_buffer_[idx] != NULL) {
    free(neteq_packet_buffer_[idx]);
    neteq_packet_buffer_[idx] = NULL;
  }

  neteq_packet_buffer_[idx] = (int16_t *) malloc(buffer_size_in_bytes);
  if (neteq_packet_buffer_[idx] == NULL) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "AllocatePacketBufferByIdxSafe: NetEq Initialization error: "
                 "could not allocate memory for NetEq Packet Buffer");
    return -1;
  }
  if (WebRtcNetEQ_AssignBuffer(inst_[idx], max_num_packets,
                               neteq_packet_buffer_[idx],
                               buffer_size_in_bytes) != 0) {
    if (neteq_packet_buffer_[idx] != NULL) {
      free(neteq_packet_buffer_[idx]);
      neteq_packet_buffer_[idx] = NULL;
    }
    LogError("AssignBuffer", idx);
    return -1;
  }
  return 0;
}

int32_t ACMNetEQ::SetAVTPlayout(const bool enable) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  if (avt_playout_ != enable) {
    for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) {
      if (!is_initialized_[idx]) {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                     "SetAVTPlayout: NetEq is not initialized.");
        return -1;
      }
      if (WebRtcNetEQ_SetAVTPlayout(inst_[idx], (enable) ? 1 : 0) < 0) {
        LogError("SetAVTPlayout", idx);
        return -1;
      }
    }
  }
  avt_playout_ = enable;
  return 0;
}

bool ACMNetEQ::avt_playout() const {
  CriticalSectionScoped lock(neteq_crit_sect_);
  return avt_playout_;
}

int32_t ACMNetEQ::CurrentSampFreqHz() const {
  CriticalSectionScoped lock(neteq_crit_sect_);
  if (!is_initialized_[0]) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "CurrentSampFreqHz: NetEq is not initialized.");
    return -1;
  }
  return (int32_t)(1000 * current_samp_freq_khz_);
}

int32_t ACMNetEQ::SetPlayoutMode(const AudioPlayoutMode mode) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  if (playout_mode_ == mode)
    return 0;

  enum WebRtcNetEQPlayoutMode playout_mode = kPlayoutOff;
  enum WebRtcNetEQBGNMode background_noise_mode = kBGNOn;
  switch (mode) {
    case voice:
      playout_mode = kPlayoutOn;
      background_noise_mode = kBGNOn;
      break;
    case fax:
      playout_mode = kPlayoutFax;
      WebRtcNetEQ_GetBGNMode(inst_[0], &background_noise_mode);  // No change.
      break;
    case streaming:
      playout_mode = kPlayoutStreaming;
      background_noise_mode = kBGNOff;
      break;
    case off:
      playout_mode = kPlayoutOff;
      background_noise_mode = kBGNOff;
      break;
  }

  int err = 0;
  for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) {
    if (!is_initialized_[idx]) {
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "SetPlayoutMode: NetEq is not initialized.");
      return -1;
    }

    if (WebRtcNetEQ_SetPlayoutMode(inst_[idx], playout_mode) < 0) {
      LogError("SetPlayoutMode", idx);
      err = -1;
    }

    if (WebRtcNetEQ_SetBGNMode(inst_[idx], kBGNOff) < 0) {
      LogError("SetPlayoutMode::SetBGNMode", idx);
      err = -1;
    }
  }
  if (err == 0)
    playout_mode_ = mode;
  return err;
}

AudioPlayoutMode ACMNetEQ::playout_mode() const {
  CriticalSectionScoped lock(neteq_crit_sect_);
  return playout_mode_;
}

int32_t ACMNetEQ::NetworkStatistics(
    ACMNetworkStatistics* statistics) const {
  WebRtcNetEQ_NetworkStatistics stats;
  CriticalSectionScoped lock(neteq_crit_sect_);
  if (!is_initialized_[0]) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "NetworkStatistics: NetEq is not initialized.");
    return -1;
  }
  if (WebRtcNetEQ_GetNetworkStatistics(inst_[0], &stats) == 0) {
    statistics->currentAccelerateRate = stats.currentAccelerateRate;
    statistics->currentBufferSize = stats.currentBufferSize;
    statistics->jitterPeaksFound = (stats.jitterPeaksFound > 0);
    statistics->currentDiscardRate = stats.currentDiscardRate;
    statistics->currentExpandRate = stats.currentExpandRate;
    statistics->currentPacketLossRate = stats.currentPacketLossRate;
    statistics->currentPreemptiveRate = stats.currentPreemptiveRate;
    statistics->preferredBufferSize = stats.preferredBufferSize;
    statistics->clockDriftPPM = stats.clockDriftPPM;
    statistics->addedSamples = stats.addedSamples;
  } else {
    LogError("getNetworkStatistics", 0);
    return -1;
  }
  const int kArrayLen = 100;
  int waiting_times[kArrayLen];
  int waiting_times_len = WebRtcNetEQ_GetRawFrameWaitingTimes(inst_[0],
                                                              kArrayLen,
                                                              waiting_times);
  if (waiting_times_len > 0) {
    std::vector<int> waiting_times_vec(waiting_times,
                                       waiting_times + waiting_times_len);
    std::sort(waiting_times_vec.begin(), waiting_times_vec.end());
    size_t size = waiting_times_vec.size();
    assert(size == static_cast<size_t>(waiting_times_len));
    if (size % 2 == 0) {
      statistics->medianWaitingTimeMs = (waiting_times_vec[size / 2 - 1] +
          waiting_times_vec[size / 2]) / 2;
    } else {
      statistics->medianWaitingTimeMs = waiting_times_vec[size / 2];
    }
    statistics->minWaitingTimeMs = waiting_times_vec.front();
    statistics->maxWaitingTimeMs = waiting_times_vec.back();
    double sum = 0;
    for (size_t i = 0; i < size; ++i) {
      sum += waiting_times_vec[i];
    }
    statistics->meanWaitingTimeMs = static_cast<int>(sum / size);
  } else if (waiting_times_len == 0) {
    statistics->meanWaitingTimeMs = -1;
    statistics->medianWaitingTimeMs = -1;
    statistics->minWaitingTimeMs = -1;
    statistics->maxWaitingTimeMs = -1;
  } else {
    LogError("getRawFrameWaitingTimes", 0);
    return -1;
  }
  return 0;
}

// Should only be called in AV-sync mode.
int ACMNetEQ::RecIn(const WebRtcRTPHeader& rtp_info,
                    uint32_t receive_timestamp) {
  assert(av_sync_);

  // Translate to NetEq structure.
  WebRtcNetEQ_RTPInfo neteq_rtpinfo;
  neteq_rtpinfo.payloadType = rtp_info.header.payloadType;
  neteq_rtpinfo.sequenceNumber = rtp_info.header.sequenceNumber;
  neteq_rtpinfo.timeStamp = rtp_info.header.timestamp;
  neteq_rtpinfo.SSRC = rtp_info.header.ssrc;
  neteq_rtpinfo.markerBit = rtp_info.header.markerBit;

  CriticalSectionScoped lock(neteq_crit_sect_);

  // Master should be initialized.
  assert(is_initialized_[0]);

  // Push into Master.
  int status = WebRtcNetEQ_RecInSyncRTP(inst_[0], &neteq_rtpinfo,
                                        receive_timestamp);
  if (status < 0) {
    LogError("RecInSyncRTP", 0);
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "RecIn (sync): NetEq, error in pushing in Master");
    return -1;
  }

  // If the received stream is stereo, insert a sync payload into slave.
  if (rtp_info.type.Audio.channel == 2) {
    // Slave should be initialized.
    assert(is_initialized_[1]);

    // PUSH into Slave
    status = WebRtcNetEQ_RecInSyncRTP(inst_[1], &neteq_rtpinfo,
                                      receive_timestamp);
    if (status < 0) {
      LogError("RecInRTPStruct", 1);
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "RecIn (sync): NetEq, error in pushing in Slave");
      return -1;
    }
  }
  return status;
}

int32_t ACMNetEQ::RecIn(const uint8_t* incoming_payload,
                        const int32_t length_payload,
                        const WebRtcRTPHeader& rtp_info,
                        uint32_t receive_timestamp) {
  int16_t payload_length = static_cast<int16_t>(length_payload);

  // Translate to NetEq structure.
  WebRtcNetEQ_RTPInfo neteq_rtpinfo;
  neteq_rtpinfo.payloadType = rtp_info.header.payloadType;
  neteq_rtpinfo.sequenceNumber = rtp_info.header.sequenceNumber;
  neteq_rtpinfo.timeStamp = rtp_info.header.timestamp;
  neteq_rtpinfo.SSRC = rtp_info.header.ssrc;
  neteq_rtpinfo.markerBit = rtp_info.header.markerBit;

  CriticalSectionScoped lock(neteq_crit_sect_);

  int status;
  // In case of stereo payload, first half of the data should be pushed into
  // master, and the second half into slave.
  if (rtp_info.type.Audio.channel == 2) {
    payload_length = payload_length / 2;
  }

  // Check that master is initialized.
  if (!is_initialized_[0]) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "RecIn: NetEq is not initialized.");
    return -1;
  }
  // Push into Master.
  status = WebRtcNetEQ_RecInRTPStruct(inst_[0], &neteq_rtpinfo,
                                      incoming_payload, payload_length,
                                      receive_timestamp);
  if (status < 0) {
    LogError("RecInRTPStruct", 0);
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "RecIn: NetEq, error in pushing in Master");
    return -1;
  }

  // If the received stream is stereo, insert second half of paket into slave.
  if (rtp_info.type.Audio.channel == 2) {
    if (!is_initialized_[1]) {
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "RecIn: NetEq is not initialized.");
      return -1;
    }
    // Push into Slave.
    status = WebRtcNetEQ_RecInRTPStruct(inst_[1], &neteq_rtpinfo,
                                        &incoming_payload[payload_length],
                                        payload_length, receive_timestamp);
    if (status < 0) {
      LogError("RecInRTPStruct", 1);
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "RecIn: NetEq, error in pushing in Slave");
      return -1;
    }
  }

  return 0;
}

int32_t ACMNetEQ::RecOut(AudioFrame& audio_frame) {
  enum WebRtcNetEQOutputType type;
  int16_t payload_len_sample;
  enum WebRtcNetEQOutputType type_master;
  enum WebRtcNetEQOutputType type_slave;

  int16_t payload_len_sample_slave;

  CriticalSectionScoped lockNetEq(neteq_crit_sect_);

  if (!received_stereo_) {
    if (!is_initialized_[0]) {
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "RecOut: NetEq is not initialized.");
      return -1;
    }
    {
      WriteLockScoped lockCodec(*decode_lock_);
      if (WebRtcNetEQ_RecOut(inst_[0], &(audio_frame.data_[0]),
                             &payload_len_sample) != 0) {
        LogError("RecOut", 0);
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                     "RecOut: NetEq, error in pulling out for mono case");
        // Check for errors that can be recovered from:
        // RECOUT_ERROR_SAMPLEUNDERRUN = 2003
        int error_code = WebRtcNetEQ_GetErrorCode(inst_[0]);
        if (error_code != 2003) {
          // Cannot recover; return an error
          return -1;
        }
      }
    }
    WebRtcNetEQ_GetSpeechOutputType(inst_[0], &type);
    audio_frame.num_channels_ = 1;
  } else {
    if (!is_initialized_[0] || !is_initialized_[1]) {
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "RecOut: NetEq is not initialized.");
      return -1;
    }
    int16_t payload_master[480];
    int16_t payload_slave[480];
    {
      WriteLockScoped lockCodec(*decode_lock_);
      if (WebRtcNetEQ_RecOutMasterSlave(inst_[0], payload_master,
                                        &payload_len_sample, master_slave_info_,
                                        1) != 0) {
        LogError("RecOutMasterSlave", 0);
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                     "RecOut: NetEq, error in pulling out for master");

        // Check for errors that can be recovered from:
        // RECOUT_ERROR_SAMPLEUNDERRUN = 2003
        int error_code = WebRtcNetEQ_GetErrorCode(inst_[0]);
        if (error_code != 2003) {
          // Cannot recover; return an error
          return -1;
        }
      }
      if (WebRtcNetEQ_RecOutMasterSlave(inst_[1], payload_slave,
                                        &payload_len_sample_slave,
                                        master_slave_info_, 0) != 0) {
        LogError("RecOutMasterSlave", 1);
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                     "RecOut: NetEq, error in pulling out for slave");

        // Check for errors that can be recovered from:
        // RECOUT_ERROR_SAMPLEUNDERRUN = 2003
        int error_code = WebRtcNetEQ_GetErrorCode(inst_[1]);
        if (error_code != 2003) {
          // Cannot recover; return an error
          return -1;
        }
      }
    }

    if (payload_len_sample != payload_len_sample_slave) {
      WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_,
                   "RecOut: mismatch between the lenght of the decoded audio "
                   "by Master (%d samples) and Slave (%d samples).",
                   payload_len_sample, payload_len_sample_slave);
      if (payload_len_sample > payload_len_sample_slave) {
        memset(&payload_slave[payload_len_sample_slave], 0,
               (payload_len_sample - payload_len_sample_slave) *
               sizeof(int16_t));
      }
    }

    for (int16_t n = 0; n < payload_len_sample; n++) {
      audio_frame.data_[n << 1] = payload_master[n];
      audio_frame.data_[(n << 1) + 1] = payload_slave[n];
    }
    audio_frame.num_channels_ = 2;

    WebRtcNetEQ_GetSpeechOutputType(inst_[0], &type_master);
    WebRtcNetEQ_GetSpeechOutputType(inst_[1], &type_slave);
    if ((type_master == kOutputNormal) || (type_slave == kOutputNormal)) {
      type = kOutputNormal;
    } else {
      type = type_master;
    }
  }

  audio_frame.samples_per_channel_ =
      static_cast<uint16_t>(payload_len_sample);
  // NetEq always returns 10 ms of audio.
  current_samp_freq_khz_ =
      static_cast<float>(audio_frame.samples_per_channel_) / 10.0f;
  audio_frame.sample_rate_hz_ = audio_frame.samples_per_channel_ * 100;
  if (vad_status_) {
    if (type == kOutputVADPassive) {
      audio_frame.vad_activity_ = AudioFrame::kVadPassive;
      audio_frame.speech_type_ = AudioFrame::kNormalSpeech;
    } else if (type == kOutputNormal) {
      audio_frame.vad_activity_ = AudioFrame::kVadActive;
      audio_frame.speech_type_ = AudioFrame::kNormalSpeech;
    } else if (type == kOutputPLC) {
      audio_frame.vad_activity_ = previous_audio_activity_;
      audio_frame.speech_type_ = AudioFrame::kPLC;
    } else if (type == kOutputCNG) {
      audio_frame.vad_activity_ = AudioFrame::kVadPassive;
      audio_frame.speech_type_ = AudioFrame::kCNG;
    } else {
      audio_frame.vad_activity_ = AudioFrame::kVadPassive;
      audio_frame.speech_type_ = AudioFrame::kPLCCNG;
    }
  } else {
    // Always return kVadUnknown when receive VAD is inactive
    audio_frame.vad_activity_ = AudioFrame::kVadUnknown;
    if (type == kOutputNormal) {
      audio_frame.speech_type_ = AudioFrame::kNormalSpeech;
    } else if (type == kOutputPLC) {
      audio_frame.speech_type_ = AudioFrame::kPLC;
    } else if (type == kOutputPLCtoCNG) {
      audio_frame.speech_type_ = AudioFrame::kPLCCNG;
    } else if (type == kOutputCNG) {
      audio_frame.speech_type_ = AudioFrame::kCNG;
    } else {
      // type is kOutputVADPassive which
      // we don't expect to get if vad_status_ is false
      WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_,
                   "RecOut: NetEq returned kVadPassive while vad_status_ is "
                   "false.");
      audio_frame.vad_activity_ = AudioFrame::kVadUnknown;
      audio_frame.speech_type_ = AudioFrame::kNormalSpeech;
    }
  }
  previous_audio_activity_ = audio_frame.vad_activity_;

  WebRtcNetEQ_ProcessingActivity processing_stats;
  WebRtcNetEQ_GetProcessingActivity(inst_[0], &processing_stats);
  WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, id_,
               "ACM::RecOut accelerate_bgn=%d accelerate_normal=%d"
               " expand_bgn=%d expand_normal=%d"
               " preemptive_bgn=%d preemptive_normal=%d"
               " merge_bgn=%d merge_normal=%d",
               processing_stats.accelerate_bgn_samples,
               processing_stats.accelerate_normal_samples,
               processing_stats.expand_bgn_sampels,
               processing_stats.expand_normal_samples,
               processing_stats.preemptive_expand_bgn_samples,
               processing_stats.preemptive_expand_normal_samples,
               processing_stats.merge_expand_bgn_samples,
               processing_stats.merge_expand_normal_samples);
  return 0;
}

// When ACMGenericCodec has set the codec specific parameters in codec_def
// it calls AddCodec() to add the new codec to the NetEQ database.
int32_t ACMNetEQ::AddCodec(WebRtcNetEQ_CodecDef* codec_def,
                           bool to_master) {
  if (codec_def == NULL) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "ACMNetEQ::AddCodec: error, codec_def is NULL");
    return -1;
  }
  CriticalSectionScoped lock(neteq_crit_sect_);

  int16_t idx;
  if (to_master) {
    idx = 0;
  } else {
    idx = 1;
  }

  if (!is_initialized_[idx]) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "ACMNetEQ::AddCodec: NetEq is not initialized.");
    return -1;
  }
  if (WebRtcNetEQ_CodecDbAdd(inst_[idx], codec_def) < 0) {
    LogError("CodecDB_Add", idx);
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "ACMNetEQ::AddCodec: NetEq, error in adding codec");
    return -1;
  } else {
    return 0;
  }
}

// Creates a Word16 RTP packet out of a Word8 payload and an rtp info struct.
// Must be byte order safe.
void ACMNetEQ::RTPPack(int16_t* rtp_packet, const int8_t* payload,
                       const int32_t payload_length_bytes,
                       const WebRtcRTPHeader& rtp_info) {
  int32_t idx = 0;
  WEBRTC_SPL_SET_BYTE(rtp_packet, (int8_t) 0x80, idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet, rtp_info.header.payloadType, idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet,
                      WEBRTC_SPL_GET_BYTE(&(rtp_info.header.sequenceNumber), 1),
                      idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet,
                      WEBRTC_SPL_GET_BYTE(&(rtp_info.header.sequenceNumber), 0),
                      idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet,
                      WEBRTC_SPL_GET_BYTE(&(rtp_info.header.timestamp), 3),
                      idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet,
                      WEBRTC_SPL_GET_BYTE(&(rtp_info.header.timestamp), 2),
                      idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet,
                      WEBRTC_SPL_GET_BYTE(&(rtp_info.header.timestamp), 1),
                      idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet,
                      WEBRTC_SPL_GET_BYTE(&(rtp_info.header.timestamp), 0),
                      idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet,
                      WEBRTC_SPL_GET_BYTE(&(rtp_info.header.ssrc), 3), idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet, WEBRTC_SPL_GET_BYTE(&(rtp_info.header.ssrc),
                                                      2), idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet, WEBRTC_SPL_GET_BYTE(&(rtp_info.header.ssrc),
                                                      1), idx);
  idx++;
  WEBRTC_SPL_SET_BYTE(rtp_packet, WEBRTC_SPL_GET_BYTE(&(rtp_info.header.ssrc),
                                                      0), idx);
  idx++;
  for (int16_t i = 0; i < payload_length_bytes; i++) {
    WEBRTC_SPL_SET_BYTE(rtp_packet, payload[i], idx);
    idx++;
  }
  if (payload_length_bytes & 1) {
    // Our 16 bits buffer is one byte too large, set that
    // last byte to zero.
    WEBRTC_SPL_SET_BYTE(rtp_packet, 0x0, idx);
  }
}

int16_t ACMNetEQ::EnableVAD() {
  CriticalSectionScoped lock(neteq_crit_sect_);
  if (vad_status_) {
    return 0;
  }
  for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) {
    if (!is_initialized_[idx]) {
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "SetVADStatus: NetEq is not initialized.");
      return -1;
    }
    // VAD was off and we have to turn it on
    if (EnableVADByIdxSafe(idx) < 0) {
      return -1;
    }

    // Set previous VAD status to PASSIVE
    previous_audio_activity_ = AudioFrame::kVadPassive;
  }
  vad_status_ = true;
  return 0;
}

ACMVADMode ACMNetEQ::vad_mode() const {
  CriticalSectionScoped lock(neteq_crit_sect_);
  return vad_mode_;
}

int16_t ACMNetEQ::SetVADMode(const ACMVADMode mode) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  if ((mode < VADNormal) || (mode > VADVeryAggr)) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "SetVADMode: NetEq error: could not set VAD mode, mode is not "
                 "supported");
    return -1;
  } else {
    for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) {
      if (!is_initialized_[idx]) {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                     "SetVADMode: NetEq is not initialized.");
        return -1;
      }
      if (WebRtcNetEQ_SetVADMode(inst_[idx], mode) < 0) {
        LogError("SetVADmode", idx);
        return -1;
      }
    }
    vad_mode_ = mode;
    return 0;
  }
}

int32_t ACMNetEQ::FlushBuffers() {
  CriticalSectionScoped lock(neteq_crit_sect_);
  for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) {
    if (!is_initialized_[idx]) {
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "FlushBuffers: NetEq is not initialized.");
      return -1;
    }
    if (WebRtcNetEQ_FlushBuffers(inst_[idx]) < 0) {
      LogError("FlushBuffers", idx);
      return -1;
    }
  }
  return 0;
}

int16_t ACMNetEQ::RemoveCodec(WebRtcNetEQDecoder codec_idx,
                              bool is_stereo) {
  // sanity check
  if ((codec_idx <= kDecoderReservedStart) ||
      (codec_idx >= kDecoderReservedEnd)) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "RemoveCodec: NetEq error: could not Remove Codec, codec "
                 "index out of range");
    return -1;
  }
  CriticalSectionScoped lock(neteq_crit_sect_);
  if (!is_initialized_[0]) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "RemoveCodec: NetEq is not initialized.");
    return -1;
  }

  if (WebRtcNetEQ_CodecDbRemove(inst_[0], codec_idx) < 0) {
    LogError("CodecDB_Remove", 0);
    return -1;
  }

  if (is_stereo) {
    if (WebRtcNetEQ_CodecDbRemove(inst_[1], codec_idx) < 0) {
      LogError("CodecDB_Remove", 1);
      return -1;
    }
  }

  return 0;
}

int16_t ACMNetEQ::SetBackgroundNoiseMode(
    const ACMBackgroundNoiseMode mode) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) {
    if (!is_initialized_[idx]) {
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "SetBackgroundNoiseMode: NetEq is not initialized.");
      return -1;
    }
    if (WebRtcNetEQ_SetBGNMode(inst_[idx], (WebRtcNetEQBGNMode) mode) < 0) {
      LogError("SetBGNMode", idx);
      return -1;
    }
  }
  return 0;
}

int16_t ACMNetEQ::BackgroundNoiseMode(ACMBackgroundNoiseMode& mode) {
  WebRtcNetEQBGNMode my_mode;
  CriticalSectionScoped lock(neteq_crit_sect_);
  if (!is_initialized_[0]) {
    WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                 "BackgroundNoiseMode: NetEq is not initialized.");
    return -1;
  }
  if (WebRtcNetEQ_GetBGNMode(inst_[0], &my_mode) < 0) {
    LogError("WebRtcNetEQ_GetBGNMode", 0);
    return -1;
  } else {
    mode = (ACMBackgroundNoiseMode) my_mode;
  }
  return 0;
}

void ACMNetEQ::set_id(int32_t id) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  id_ = id;
}

void ACMNetEQ::LogError(const char* neteq_func_name,
                        const int16_t idx) const {
  char error_name[NETEQ_ERR_MSG_LEN_BYTE];
  char my_func_name[50];
  int neteq_error_code = WebRtcNetEQ_GetErrorCode(inst_[idx]);
  WebRtcNetEQ_GetErrorName(neteq_error_code, error_name,
                           NETEQ_ERR_MSG_LEN_BYTE - 1);
  strncpy(my_func_name, neteq_func_name, 49);
  error_name[NETEQ_ERR_MSG_LEN_BYTE - 1] = '\0';
  my_func_name[49] = '\0';
  WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
               "NetEq-%d Error in function %s, error-code: %d, error-string: "
               " %s", idx, my_func_name, neteq_error_code, error_name);
}

int32_t ACMNetEQ::PlayoutTimestamp(uint32_t& timestamp) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  if (WebRtcNetEQ_GetSpeechTimeStamp(inst_[0], &timestamp) < 0) {
    LogError("GetSpeechTimeStamp", 0);
    return -1;
  } else {
    return 0;
  }
}

void ACMNetEQ::RemoveSlaves() {
  CriticalSectionScoped lock(neteq_crit_sect_);
  RemoveSlavesSafe();
}

void ACMNetEQ::RemoveSlavesSafe() {
  for (int i = 1; i < num_slaves_ + 1; i++) {
    RemoveNetEQSafe(i);
  }

  if (master_slave_info_ != NULL) {
    free(master_slave_info_);
    master_slave_info_ = NULL;
  }
  num_slaves_ = 0;
}

void ACMNetEQ::RemoveNetEQSafe(int index) {
  if (inst_mem_[index] != NULL) {
    free(inst_mem_[index]);
    inst_mem_[index] = NULL;
    inst_[index] = NULL;
  }
  if (neteq_packet_buffer_[index] != NULL) {
    free(neteq_packet_buffer_[index]);
    neteq_packet_buffer_[index] = NULL;
  }
  if (ptr_vadinst_[index] != NULL) {
    WebRtcVad_Free(ptr_vadinst_[index]);
    ptr_vadinst_[index] = NULL;
  }
}

int16_t ACMNetEQ::AddSlave(const WebRtcNetEQDecoder* used_codecs,
                           int16_t num_codecs) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  const int16_t slave_idx = 1;
  if (num_slaves_ < 1) {
    // initialize the receiver, this also sets up VAD.
    if (InitByIdxSafe(slave_idx) < 0) {
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "AddSlave: AddSlave Failed, Could not Initialize");
      return -1;
    }

    // Allocate buffer.
    if (AllocatePacketBufferByIdxSafe(used_codecs, num_codecs,
                                      slave_idx) < 0) {
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "AddSlave: AddSlave Failed, Could not Allocate Packet "
                   "Buffer");
      return -1;
    }

    if (master_slave_info_ != NULL) {
      free(master_slave_info_);
      master_slave_info_ = NULL;
    }
    int ms_info_size = WebRtcNetEQ_GetMasterSlaveInfoSize();
    master_slave_info_ = malloc(ms_info_size);

    if (master_slave_info_ == NULL) {
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "AddSlave: AddSlave Failed, Could not Allocate memory for "
                   "Master-Slave Info");
      return -1;
    }

    // We accept this as initialized NetEQ, the rest is to synchronize
    // Slave with Master.
    num_slaves_ = 1;
    is_initialized_[slave_idx] = true;

    // Set AVT
    if (WebRtcNetEQ_SetAVTPlayout(inst_[slave_idx],
                                  (avt_playout_) ? 1 : 0) < 0) {
      LogError("SetAVTPlayout", slave_idx);
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "AddSlave: AddSlave Failed, Could not set AVT playout.");
      return -1;
    }

    // Set Background Noise
    WebRtcNetEQBGNMode current_mode;
    if (WebRtcNetEQ_GetBGNMode(inst_[0], &current_mode) < 0) {
      LogError("GetBGNMode", 0);
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "AAddSlave: AddSlave Failed, Could not Get BGN form "
                   "Master.");
      return -1;
    }

    if (WebRtcNetEQ_SetBGNMode(inst_[slave_idx],
                               (WebRtcNetEQBGNMode) current_mode) < 0) {
      LogError("SetBGNMode", slave_idx);
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "AddSlave: AddSlave Failed, Could not set BGN mode.");
      return -1;
    }

    enum WebRtcNetEQPlayoutMode playout_mode = kPlayoutOff;
    switch (playout_mode_) {
      case voice:
        playout_mode = kPlayoutOn;
        break;
      case fax:
        playout_mode = kPlayoutFax;
        break;
      case streaming:
        playout_mode = kPlayoutStreaming;
        break;
      case off:
        playout_mode = kPlayoutOff;
        break;
    }
    if (WebRtcNetEQ_SetPlayoutMode(inst_[slave_idx], playout_mode) < 0) {
      LogError("SetPlayoutMode", 1);
      WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_,
                   "AddSlave: AddSlave Failed, Could not Set Playout Mode.");
      return -1;
    }

    // Set AV-sync for the slave.
    WebRtcNetEQ_EnableAVSync(inst_[slave_idx], av_sync_ ? 1 : 0);

    // Set minimum delay.
    if (minimum_delay_ms_ > 0)
      WebRtcNetEQ_SetMinimumDelay(inst_[slave_idx], minimum_delay_ms_);

    // Set maximum delay.
    if (maximum_delay_ms_ > 0)
      WebRtcNetEQ_SetMaximumDelay(inst_[slave_idx], maximum_delay_ms_);
  }

  return 0;
}

void ACMNetEQ::set_received_stereo(bool received_stereo) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  received_stereo_ = received_stereo;
}

uint8_t ACMNetEQ::num_slaves() {
  CriticalSectionScoped lock(neteq_crit_sect_);
  return num_slaves_;
}

void ACMNetEQ::EnableAVSync(bool enable) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  av_sync_ = enable;
  for (int i = 0; i < num_slaves_ + 1; ++i) {
    assert(is_initialized_[i]);
    WebRtcNetEQ_EnableAVSync(inst_[i], enable ? 1 : 0);
  }
}

int ACMNetEQ::SetMinimumDelay(int minimum_delay_ms) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  for (int i = 0; i < num_slaves_ + 1; ++i) {
    assert(is_initialized_[i]);
    if (WebRtcNetEQ_SetMinimumDelay(inst_[i], minimum_delay_ms) < 0)
      return -1;
  }
  minimum_delay_ms_ = minimum_delay_ms;
  return 0;
}

int ACMNetEQ::SetMaximumDelay(int maximum_delay_ms) {
  CriticalSectionScoped lock(neteq_crit_sect_);
  for (int i = 0; i < num_slaves_ + 1; ++i) {
    assert(is_initialized_[i]);
    if (WebRtcNetEQ_SetMaximumDelay(inst_[i], maximum_delay_ms) < 0)
      return -1;
  }
  maximum_delay_ms_ = maximum_delay_ms;
  return 0;
}

int ACMNetEQ::LeastRequiredDelayMs() const {
  CriticalSectionScoped lock(neteq_crit_sect_);
  assert(is_initialized_[0]);

  // Sufficient to query the master.
  return WebRtcNetEQ_GetRequiredDelayMs(inst_[0]);
}

bool ACMNetEQ::DecodedRtpInfo(int* sequence_number, uint32_t* timestamp) const {
  CriticalSectionScoped lock(neteq_crit_sect_);
  if (WebRtcNetEQ_DecodedRtpInfo(inst_[0], sequence_number, timestamp) < 0)
    return false;
  return true;
}

}  // namespace acm1

}  // namespace webrtc
