/*
 *  Copyright (c) 2015 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 <algorithm>
#include <memory>
#include <vector>

#include "api/array_view.h"
#include "modules/audio_processing/audio_processing_impl.h"
#include "modules/audio_processing/test/audio_processing_builder_for_testing.h"
#include "modules/audio_processing/test/test_utils.h"
#include "rtc_base/event.h"
#include "rtc_base/platform_thread.h"
#include "rtc_base/random.h"
#include "rtc_base/synchronization/mutex.h"
#include "system_wrappers/include/sleep.h"
#include "test/gtest.h"

namespace webrtc {
namespace {

constexpr int kMaxFrameSize = 480;
constexpr TimeDelta kTestTimeOutLimit = TimeDelta::Minutes(10);

class AudioProcessingImplLockTest;

// Type of the render thread APM API call to use in the test.
enum class RenderApiImpl {
  ProcessReverseStreamImplInteger,
  ProcessReverseStreamImplFloat,
  AnalyzeReverseStreamImplFloat,
};

// Type of the capture thread APM API call to use in the test.
enum class CaptureApiImpl { ProcessStreamImplInteger, ProcessStreamImplFloat };

// The runtime parameter setting scheme to use in the test.
enum class RuntimeParameterSettingScheme {
  SparseStreamMetadataChangeScheme,
  ExtremeStreamMetadataChangeScheme,
  FixedMonoStreamMetadataScheme,
  FixedStereoStreamMetadataScheme
};

// Variant of echo canceller settings to use in the test.
enum class AecType {
  BasicWebRtcAecSettings,
  AecTurnedOff,
  BasicWebRtcAecSettingsWithExtentedFilter,
  BasicWebRtcAecSettingsWithDelayAgnosticAec,
  BasicWebRtcAecSettingsWithAecMobile
};

// Thread-safe random number generator wrapper.
class RandomGenerator {
 public:
  RandomGenerator() : rand_gen_(42U) {}

  int RandInt(int min, int max) {
    MutexLock lock(&mutex_);
    return rand_gen_.Rand(min, max);
  }

  int RandInt(int max) {
    MutexLock lock(&mutex_);
    return rand_gen_.Rand(max);
  }

  float RandFloat() {
    MutexLock lock(&mutex_);
    return rand_gen_.Rand<float>();
  }

 private:
  Mutex mutex_;
  Random rand_gen_ RTC_GUARDED_BY(mutex_);
};

// Variables related to the audio data and formats.
struct AudioFrameData {
  explicit AudioFrameData(int max_frame_size) {
    // Set up the two-dimensional arrays needed for the APM API calls.
    input_framechannels.resize(2 * max_frame_size);
    input_frame.resize(2);
    input_frame[0] = &input_framechannels[0];
    input_frame[1] = &input_framechannels[max_frame_size];

    output_frame_channels.resize(2 * max_frame_size);
    output_frame.resize(2);
    output_frame[0] = &output_frame_channels[0];
    output_frame[1] = &output_frame_channels[max_frame_size];

    frame.resize(2 * max_frame_size);
  }

  std::vector<int16_t> frame;

  std::vector<float*> output_frame;
  std::vector<float> output_frame_channels;
  std::vector<float*> input_frame;
  std::vector<float> input_framechannels;

  int input_sample_rate_hz = 16000;
  int input_number_of_channels = 1;
  int output_sample_rate_hz = 16000;
  int output_number_of_channels = 1;
};

// The configuration for the test.
struct TestConfig {
  // Test case generator for the test configurations to use in the brief tests.
  static std::vector<TestConfig> GenerateBriefTestConfigs() {
    std::vector<TestConfig> test_configs;
    AecType aec_types[] = {AecType::BasicWebRtcAecSettingsWithDelayAgnosticAec,
                           AecType::BasicWebRtcAecSettingsWithAecMobile};
    for (auto aec_type : aec_types) {
      TestConfig test_config;
      test_config.aec_type = aec_type;

      test_config.min_number_of_calls = 300;

      // Perform tests only with the extreme runtime parameter setting scheme.
      test_config.runtime_parameter_setting_scheme =
          RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme;

      // Only test 16 kHz for this test suite.
      test_config.initial_sample_rate_hz = 16000;

      // Create test config for the Int16 processing API function set.
      test_config.render_api_function =
          RenderApiImpl::ProcessReverseStreamImplInteger;
      test_config.capture_api_function =
          CaptureApiImpl::ProcessStreamImplInteger;
      test_configs.push_back(test_config);

      // Create test config for the StreamConfig processing API function set.
      test_config.render_api_function =
          RenderApiImpl::ProcessReverseStreamImplFloat;
      test_config.capture_api_function = CaptureApiImpl::ProcessStreamImplFloat;
      test_configs.push_back(test_config);
    }

    // Return the created test configurations.
    return test_configs;
  }

  // Test case generator for the test configurations to use in the extensive
  // tests.
  static std::vector<TestConfig> GenerateExtensiveTestConfigs() {
    // Lambda functions for the test config generation.
    auto add_processing_apis = [](TestConfig test_config) {
      struct AllowedApiCallCombinations {
        RenderApiImpl render_api;
        CaptureApiImpl capture_api;
      };

      const AllowedApiCallCombinations api_calls[] = {
          {RenderApiImpl::ProcessReverseStreamImplInteger,
           CaptureApiImpl::ProcessStreamImplInteger},
          {RenderApiImpl::ProcessReverseStreamImplFloat,
           CaptureApiImpl::ProcessStreamImplFloat},
          {RenderApiImpl::AnalyzeReverseStreamImplFloat,
           CaptureApiImpl::ProcessStreamImplFloat},
          {RenderApiImpl::ProcessReverseStreamImplInteger,
           CaptureApiImpl::ProcessStreamImplFloat},
          {RenderApiImpl::ProcessReverseStreamImplFloat,
           CaptureApiImpl::ProcessStreamImplInteger}};
      std::vector<TestConfig> out;
      for (auto api_call : api_calls) {
        test_config.render_api_function = api_call.render_api;
        test_config.capture_api_function = api_call.capture_api;
        out.push_back(test_config);
      }
      return out;
    };

    auto add_aec_settings = [](const std::vector<TestConfig>& in) {
      std::vector<TestConfig> out;
      AecType aec_types[] = {
          AecType::BasicWebRtcAecSettings, AecType::AecTurnedOff,
          AecType::BasicWebRtcAecSettingsWithExtentedFilter,
          AecType::BasicWebRtcAecSettingsWithDelayAgnosticAec,
          AecType::BasicWebRtcAecSettingsWithAecMobile};
      for (auto test_config : in) {
        // Due to a VisualStudio 2015 compiler issue, the internal loop
        // variable here cannot override a previously defined name.
        // In other words "type" cannot be named "aec_type" here.
        // https://connect.microsoft.com/VisualStudio/feedback/details/2291755
        for (auto type : aec_types) {
          test_config.aec_type = type;
          out.push_back(test_config);
        }
      }
      return out;
    };

    auto add_settings_scheme = [](const std::vector<TestConfig>& in) {
      std::vector<TestConfig> out;
      RuntimeParameterSettingScheme schemes[] = {
          RuntimeParameterSettingScheme::SparseStreamMetadataChangeScheme,
          RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme,
          RuntimeParameterSettingScheme::FixedMonoStreamMetadataScheme,
          RuntimeParameterSettingScheme::FixedStereoStreamMetadataScheme};

      for (auto test_config : in) {
        for (auto scheme : schemes) {
          test_config.runtime_parameter_setting_scheme = scheme;
          out.push_back(test_config);
        }
      }
      return out;
    };

    auto add_sample_rates = [](const std::vector<TestConfig>& in) {
      const int sample_rates[] = {8000, 16000, 32000, 48000};

      std::vector<TestConfig> out;
      for (auto test_config : in) {
        auto available_rates =
            (test_config.aec_type ==
                     AecType::BasicWebRtcAecSettingsWithAecMobile
                 ? rtc::ArrayView<const int>(sample_rates, 2)
                 : rtc::ArrayView<const int>(sample_rates));

        for (auto rate : available_rates) {
          test_config.initial_sample_rate_hz = rate;
          out.push_back(test_config);
        }
      }
      return out;
    };

    // Generate test configurations of the relevant combinations of the
    // parameters to
    // test.
    TestConfig test_config;
    test_config.min_number_of_calls = 10000;
    return add_sample_rates(add_settings_scheme(
        add_aec_settings(add_processing_apis(test_config))));
  }

  RenderApiImpl render_api_function =
      RenderApiImpl::ProcessReverseStreamImplFloat;
  CaptureApiImpl capture_api_function = CaptureApiImpl::ProcessStreamImplFloat;
  RuntimeParameterSettingScheme runtime_parameter_setting_scheme =
      RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme;
  int initial_sample_rate_hz = 16000;
  AecType aec_type = AecType::BasicWebRtcAecSettingsWithDelayAgnosticAec;
  int min_number_of_calls = 300;
};

// Handler for the frame counters.
class FrameCounters {
 public:
  void IncreaseRenderCounter() {
    MutexLock lock(&mutex_);
    render_count++;
  }

  void IncreaseCaptureCounter() {
    MutexLock lock(&mutex_);
    capture_count++;
  }

  int GetCaptureCounter() const {
    MutexLock lock(&mutex_);
    return capture_count;
  }

  int GetRenderCounter() const {
    MutexLock lock(&mutex_);
    return render_count;
  }

  int CaptureMinusRenderCounters() const {
    MutexLock lock(&mutex_);
    return capture_count - render_count;
  }

  int RenderMinusCaptureCounters() const {
    return -CaptureMinusRenderCounters();
  }

  bool BothCountersExceedeThreshold(int threshold) {
    MutexLock lock(&mutex_);
    return (render_count > threshold && capture_count > threshold);
  }

 private:
  mutable Mutex mutex_;
  int render_count RTC_GUARDED_BY(mutex_) = 0;
  int capture_count RTC_GUARDED_BY(mutex_) = 0;
};

// Class for handling the capture side processing.
class CaptureProcessor {
 public:
  CaptureProcessor(int max_frame_size,
                   RandomGenerator* rand_gen,
                   rtc::Event* render_call_event,
                   rtc::Event* capture_call_event,
                   FrameCounters* shared_counters_state,
                   const TestConfig* test_config,
                   AudioProcessing* apm);
  void Process();

 private:
  static constexpr int kMaxCallDifference = 10;
  static constexpr float kCaptureInputFloatLevel = 0.03125f;
  static constexpr int kCaptureInputFixLevel = 1024;

  void PrepareFrame();
  void CallApmCaptureSide();
  void ApplyRuntimeSettingScheme();

  RandomGenerator* const rand_gen_ = nullptr;
  rtc::Event* const render_call_event_ = nullptr;
  rtc::Event* const capture_call_event_ = nullptr;
  FrameCounters* const frame_counters_ = nullptr;
  const TestConfig* const test_config_ = nullptr;
  AudioProcessing* const apm_ = nullptr;
  AudioFrameData frame_data_;
};

// Class for handling the stats processing.
class StatsProcessor {
 public:
  StatsProcessor(RandomGenerator* rand_gen,
                 const TestConfig* test_config,
                 AudioProcessing* apm);
  void Process();

 private:
  RandomGenerator* rand_gen_ = nullptr;
  const TestConfig* const test_config_ = nullptr;
  AudioProcessing* apm_ = nullptr;
};

// Class for handling the render side processing.
class RenderProcessor {
 public:
  RenderProcessor(int max_frame_size,
                  RandomGenerator* rand_gen,
                  rtc::Event* render_call_event,
                  rtc::Event* capture_call_event,
                  FrameCounters* shared_counters_state,
                  const TestConfig* test_config,
                  AudioProcessing* apm);
  void Process();

 private:
  static constexpr int kMaxCallDifference = 10;
  static constexpr int kRenderInputFixLevel = 16384;
  static constexpr float kRenderInputFloatLevel = 0.5f;

  void PrepareFrame();
  void CallApmRenderSide();
  void ApplyRuntimeSettingScheme();

  RandomGenerator* const rand_gen_ = nullptr;
  rtc::Event* const render_call_event_ = nullptr;
  rtc::Event* const capture_call_event_ = nullptr;
  FrameCounters* const frame_counters_ = nullptr;
  const TestConfig* const test_config_ = nullptr;
  AudioProcessing* const apm_ = nullptr;
  AudioFrameData frame_data_;
  bool first_render_call_ = true;
};

class AudioProcessingImplLockTest
    : public ::testing::TestWithParam<TestConfig> {
 public:
  AudioProcessingImplLockTest();
  bool RunTest();
  bool MaybeEndTest();

 private:
  void SetUp() override;
  void TearDown() override;

  // Tests whether all the required render and capture side calls have been
  // done.
  bool TestDone() {
    return frame_counters_.BothCountersExceedeThreshold(
        test_config_.min_number_of_calls);
  }

  // Start the threads used in the test.
  void StartThreads() {
    const auto attributes =
        rtc::ThreadAttributes().SetPriority(rtc::ThreadPriority::kRealtime);
    render_thread_ = rtc::PlatformThread::SpawnJoinable(
        [this] {
          while (!MaybeEndTest())
            render_thread_state_.Process();
        },
        "render", attributes);
    capture_thread_ = rtc::PlatformThread::SpawnJoinable(
        [this] {
          while (!MaybeEndTest()) {
            capture_thread_state_.Process();
          }
        },
        "capture", attributes);

    stats_thread_ = rtc::PlatformThread::SpawnJoinable(
        [this] {
          while (!MaybeEndTest())
            stats_thread_state_.Process();
        },
        "stats", attributes);
  }

  // Event handlers for the test.
  rtc::Event test_complete_;
  rtc::Event render_call_event_;
  rtc::Event capture_call_event_;

  // Thread related variables.
  mutable RandomGenerator rand_gen_;

  const TestConfig test_config_;
  rtc::scoped_refptr<AudioProcessing> apm_;
  FrameCounters frame_counters_;
  RenderProcessor render_thread_state_;
  CaptureProcessor capture_thread_state_;
  StatsProcessor stats_thread_state_;
  rtc::PlatformThread render_thread_;
  rtc::PlatformThread capture_thread_;
  rtc::PlatformThread stats_thread_;
};

// Sleeps a random time between 0 and max_sleep milliseconds.
void SleepRandomMs(int max_sleep, RandomGenerator* rand_gen) {
  int sleeptime = rand_gen->RandInt(0, max_sleep);
  SleepMs(sleeptime);
}

// Populates a float audio frame with random data.
void PopulateAudioFrame(float** frame,
                        float amplitude,
                        size_t num_channels,
                        size_t samples_per_channel,
                        RandomGenerator* rand_gen) {
  for (size_t ch = 0; ch < num_channels; ch++) {
    for (size_t k = 0; k < samples_per_channel; k++) {
      // Store random 16 bit quantized float number between +-amplitude.
      frame[ch][k] = amplitude * (2 * rand_gen->RandFloat() - 1);
    }
  }
}

// Populates an integer audio frame with random data.
void PopulateAudioFrame(float amplitude,
                        size_t num_channels,
                        size_t samples_per_channel,
                        rtc::ArrayView<int16_t> frame,
                        RandomGenerator* rand_gen) {
  ASSERT_GT(amplitude, 0);
  ASSERT_LE(amplitude, 32767);
  for (size_t ch = 0; ch < num_channels; ch++) {
    for (size_t k = 0; k < samples_per_channel; k++) {
      // Store random 16 bit number between -(amplitude+1) and
      // amplitude.
      frame[k * ch] = rand_gen->RandInt(2 * amplitude + 1) - amplitude - 1;
    }
  }
}

AudioProcessing::Config GetApmTestConfig(AecType aec_type) {
  AudioProcessing::Config apm_config;
  apm_config.echo_canceller.enabled = aec_type != AecType::AecTurnedOff;
  apm_config.echo_canceller.mobile_mode =
      aec_type == AecType::BasicWebRtcAecSettingsWithAecMobile;
  apm_config.gain_controller1.enabled = true;
  apm_config.gain_controller1.mode =
      AudioProcessing::Config::GainController1::kAdaptiveDigital;
  apm_config.noise_suppression.enabled = true;
  return apm_config;
}

AudioProcessingImplLockTest::AudioProcessingImplLockTest()
    : test_config_(GetParam()),
      apm_(AudioProcessingBuilderForTesting()
               .SetConfig(GetApmTestConfig(test_config_.aec_type))
               .Create()),
      render_thread_state_(kMaxFrameSize,
                           &rand_gen_,
                           &render_call_event_,
                           &capture_call_event_,
                           &frame_counters_,
                           &test_config_,
                           apm_.get()),
      capture_thread_state_(kMaxFrameSize,
                            &rand_gen_,
                            &render_call_event_,
                            &capture_call_event_,
                            &frame_counters_,
                            &test_config_,
                            apm_.get()),
      stats_thread_state_(&rand_gen_, &test_config_, apm_.get()) {}

// Run the test with a timeout.
bool AudioProcessingImplLockTest::RunTest() {
  StartThreads();
  return test_complete_.Wait(kTestTimeOutLimit);
}

bool AudioProcessingImplLockTest::MaybeEndTest() {
  if (HasFatalFailure() || TestDone()) {
    test_complete_.Set();
    return true;
  }
  return false;
}

void AudioProcessingImplLockTest::SetUp() {}

void AudioProcessingImplLockTest::TearDown() {
  render_call_event_.Set();
  capture_call_event_.Set();
}

StatsProcessor::StatsProcessor(RandomGenerator* rand_gen,
                               const TestConfig* test_config,
                               AudioProcessing* apm)
    : rand_gen_(rand_gen), test_config_(test_config), apm_(apm) {}

// Implements the callback functionality for the statistics
// collection thread.
void StatsProcessor::Process() {
  SleepRandomMs(100, rand_gen_);

  AudioProcessing::Config apm_config = apm_->GetConfig();
  if (test_config_->aec_type != AecType::AecTurnedOff) {
    EXPECT_TRUE(apm_config.echo_canceller.enabled);
    EXPECT_EQ(apm_config.echo_canceller.mobile_mode,
              (test_config_->aec_type ==
               AecType::BasicWebRtcAecSettingsWithAecMobile));
  } else {
    EXPECT_FALSE(apm_config.echo_canceller.enabled);
  }
  EXPECT_TRUE(apm_config.gain_controller1.enabled);
  EXPECT_TRUE(apm_config.noise_suppression.enabled);

  // The below return value is not testable.
  apm_->GetStatistics();
}

CaptureProcessor::CaptureProcessor(int max_frame_size,
                                   RandomGenerator* rand_gen,
                                   rtc::Event* render_call_event,
                                   rtc::Event* capture_call_event,
                                   FrameCounters* shared_counters_state,
                                   const TestConfig* test_config,
                                   AudioProcessing* apm)
    : rand_gen_(rand_gen),
      render_call_event_(render_call_event),
      capture_call_event_(capture_call_event),
      frame_counters_(shared_counters_state),
      test_config_(test_config),
      apm_(apm),
      frame_data_(max_frame_size) {}

// Implements the callback functionality for the capture thread.
void CaptureProcessor::Process() {
  // Sleep a random time to simulate thread jitter.
  SleepRandomMs(3, rand_gen_);

  // Ensure that the number of render and capture calls do not
  // differ too much.
  if (frame_counters_->CaptureMinusRenderCounters() > kMaxCallDifference) {
    render_call_event_->Wait(rtc::Event::kForever);
  }

  // Apply any specified capture side APM non-processing runtime calls.
  ApplyRuntimeSettingScheme();

  // Apply the capture side processing call.
  CallApmCaptureSide();

  // Increase the number of capture-side calls.
  frame_counters_->IncreaseCaptureCounter();

  // Flag to the render thread that another capture API call has occurred
  // by triggering this threads call event.
  capture_call_event_->Set();
}

// Prepares a frame with relevant audio data and metadata.
void CaptureProcessor::PrepareFrame() {
  // Restrict to a common fixed sample rate if the integer
  // interface is used.
  if (test_config_->capture_api_function ==
      CaptureApiImpl::ProcessStreamImplInteger) {
    frame_data_.input_sample_rate_hz = test_config_->initial_sample_rate_hz;
    frame_data_.output_sample_rate_hz = test_config_->initial_sample_rate_hz;
  }

  // Prepare the audio data.
  StreamConfig input_stream_config(frame_data_.input_sample_rate_hz,
                                   frame_data_.input_number_of_channels);

  PopulateAudioFrame(kCaptureInputFixLevel, input_stream_config.num_channels(),
                     input_stream_config.num_frames(), frame_data_.frame,
                     rand_gen_);

  PopulateAudioFrame(&frame_data_.input_frame[0], kCaptureInputFloatLevel,
                     input_stream_config.num_channels(),
                     input_stream_config.num_frames(), rand_gen_);
}

// Applies the capture side processing API call.
void CaptureProcessor::CallApmCaptureSide() {
  // Prepare a proper capture side processing API call input.
  PrepareFrame();

  // Set the stream delay.
  apm_->set_stream_delay_ms(30);

  // Set the analog level.
  apm_->set_stream_analog_level(80);

  // Call the specified capture side API processing method.
  StreamConfig input_stream_config(frame_data_.input_sample_rate_hz,
                                   frame_data_.input_number_of_channels);
  StreamConfig output_stream_config(frame_data_.output_sample_rate_hz,
                                    frame_data_.output_number_of_channels);
  int result = AudioProcessing::kNoError;
  switch (test_config_->capture_api_function) {
    case CaptureApiImpl::ProcessStreamImplInteger:
      result =
          apm_->ProcessStream(frame_data_.frame.data(), input_stream_config,
                              output_stream_config, frame_data_.frame.data());
      break;
    case CaptureApiImpl::ProcessStreamImplFloat:
      result = apm_->ProcessStream(&frame_data_.input_frame[0],
                                   input_stream_config, output_stream_config,
                                   &frame_data_.output_frame[0]);
      break;
    default:
      FAIL();
  }

  // Retrieve the new analog level.
  apm_->recommended_stream_analog_level();

  // Check the return code for error.
  ASSERT_EQ(AudioProcessing::kNoError, result);
}

// Applies any runtime capture APM API calls and audio stream characteristics
// specified by the scheme for the test.
void CaptureProcessor::ApplyRuntimeSettingScheme() {
  const int capture_count_local = frame_counters_->GetCaptureCounter();

  // Update the number of channels and sample rates for the input and output.
  // Note that the counts frequencies for when to set parameters
  // are set using prime numbers in order to ensure that the
  // permutation scheme in the parameter setting changes.
  switch (test_config_->runtime_parameter_setting_scheme) {
    case RuntimeParameterSettingScheme::SparseStreamMetadataChangeScheme:
      if (capture_count_local == 0)
        frame_data_.input_sample_rate_hz = 16000;
      else if (capture_count_local % 11 == 0)
        frame_data_.input_sample_rate_hz = 32000;
      else if (capture_count_local % 73 == 0)
        frame_data_.input_sample_rate_hz = 48000;
      else if (capture_count_local % 89 == 0)
        frame_data_.input_sample_rate_hz = 16000;
      else if (capture_count_local % 97 == 0)
        frame_data_.input_sample_rate_hz = 8000;

      if (capture_count_local == 0)
        frame_data_.input_number_of_channels = 1;
      else if (capture_count_local % 4 == 0)
        frame_data_.input_number_of_channels =
            (frame_data_.input_number_of_channels == 1 ? 2 : 1);

      if (capture_count_local == 0)
        frame_data_.output_sample_rate_hz = 16000;
      else if (capture_count_local % 5 == 0)
        frame_data_.output_sample_rate_hz = 32000;
      else if (capture_count_local % 47 == 0)
        frame_data_.output_sample_rate_hz = 48000;
      else if (capture_count_local % 53 == 0)
        frame_data_.output_sample_rate_hz = 16000;
      else if (capture_count_local % 71 == 0)
        frame_data_.output_sample_rate_hz = 8000;

      if (capture_count_local == 0)
        frame_data_.output_number_of_channels = 1;
      else if (capture_count_local % 8 == 0)
        frame_data_.output_number_of_channels =
            (frame_data_.output_number_of_channels == 1 ? 2 : 1);
      break;
    case RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme:
      if (capture_count_local % 2 == 0) {
        frame_data_.input_number_of_channels = 1;
        frame_data_.input_sample_rate_hz = 16000;
        frame_data_.output_number_of_channels = 1;
        frame_data_.output_sample_rate_hz = 16000;
      } else {
        frame_data_.input_number_of_channels =
            (frame_data_.input_number_of_channels == 1 ? 2 : 1);
        if (frame_data_.input_sample_rate_hz == 8000)
          frame_data_.input_sample_rate_hz = 16000;
        else if (frame_data_.input_sample_rate_hz == 16000)
          frame_data_.input_sample_rate_hz = 32000;
        else if (frame_data_.input_sample_rate_hz == 32000)
          frame_data_.input_sample_rate_hz = 48000;
        else if (frame_data_.input_sample_rate_hz == 48000)
          frame_data_.input_sample_rate_hz = 8000;

        frame_data_.output_number_of_channels =
            (frame_data_.output_number_of_channels == 1 ? 2 : 1);
        if (frame_data_.output_sample_rate_hz == 8000)
          frame_data_.output_sample_rate_hz = 16000;
        else if (frame_data_.output_sample_rate_hz == 16000)
          frame_data_.output_sample_rate_hz = 32000;
        else if (frame_data_.output_sample_rate_hz == 32000)
          frame_data_.output_sample_rate_hz = 48000;
        else if (frame_data_.output_sample_rate_hz == 48000)
          frame_data_.output_sample_rate_hz = 8000;
      }
      break;
    case RuntimeParameterSettingScheme::FixedMonoStreamMetadataScheme:
      if (capture_count_local == 0) {
        frame_data_.input_sample_rate_hz = 16000;
        frame_data_.input_number_of_channels = 1;
        frame_data_.output_sample_rate_hz = 16000;
        frame_data_.output_number_of_channels = 1;
      }
      break;
    case RuntimeParameterSettingScheme::FixedStereoStreamMetadataScheme:
      if (capture_count_local == 0) {
        frame_data_.input_sample_rate_hz = 16000;
        frame_data_.input_number_of_channels = 2;
        frame_data_.output_sample_rate_hz = 16000;
        frame_data_.output_number_of_channels = 2;
      }
      break;
    default:
      FAIL();
  }

  // Call any specified runtime APM setter and
  // getter calls.
  switch (test_config_->runtime_parameter_setting_scheme) {
    case RuntimeParameterSettingScheme::SparseStreamMetadataChangeScheme:
    case RuntimeParameterSettingScheme::FixedMonoStreamMetadataScheme:
      break;
    case RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme:
    case RuntimeParameterSettingScheme::FixedStereoStreamMetadataScheme:
      if (capture_count_local % 2 == 0) {
        ASSERT_EQ(AudioProcessing::Error::kNoError,
                  apm_->set_stream_delay_ms(30));
        apm_->set_stream_key_pressed(true);
      } else {
        ASSERT_EQ(AudioProcessing::Error::kNoError,
                  apm_->set_stream_delay_ms(50));
        apm_->set_stream_key_pressed(false);
      }
      break;
    default:
      FAIL();
  }

  // Restric the number of output channels not to exceed
  // the number of input channels.
  frame_data_.output_number_of_channels =
      std::min(frame_data_.output_number_of_channels,
               frame_data_.input_number_of_channels);
}

RenderProcessor::RenderProcessor(int max_frame_size,
                                 RandomGenerator* rand_gen,
                                 rtc::Event* render_call_event,
                                 rtc::Event* capture_call_event,
                                 FrameCounters* shared_counters_state,
                                 const TestConfig* test_config,
                                 AudioProcessing* apm)
    : rand_gen_(rand_gen),
      render_call_event_(render_call_event),
      capture_call_event_(capture_call_event),
      frame_counters_(shared_counters_state),
      test_config_(test_config),
      apm_(apm),
      frame_data_(max_frame_size) {}

// Implements the callback functionality for the render thread.
void RenderProcessor::Process() {
  // Conditional wait to ensure that a capture call has been done
  // before the first render call is performed (implicitly
  // required by the APM API).
  if (first_render_call_) {
    capture_call_event_->Wait(rtc::Event::kForever);
    first_render_call_ = false;
  }

  // Sleep a random time to simulate thread jitter.
  SleepRandomMs(3, rand_gen_);

  // Ensure that the number of render and capture calls do not
  // differ too much.
  if (frame_counters_->RenderMinusCaptureCounters() > kMaxCallDifference) {
    capture_call_event_->Wait(rtc::Event::kForever);
  }

  // Apply any specified render side APM non-processing runtime calls.
  ApplyRuntimeSettingScheme();

  // Apply the render side processing call.
  CallApmRenderSide();

  // Increase the number of render-side calls.
  frame_counters_->IncreaseRenderCounter();

  // Flag to the capture thread that another render API call has occurred
  // by triggering this threads call event.
  render_call_event_->Set();
}

// Prepares the render side frame and the accompanying metadata
// with the appropriate information.
void RenderProcessor::PrepareFrame() {
  // Restrict to a common fixed sample rate if the integer interface is
  // used.
  if ((test_config_->render_api_function ==
       RenderApiImpl::ProcessReverseStreamImplInteger) ||
      (test_config_->aec_type !=
       AecType::BasicWebRtcAecSettingsWithAecMobile)) {
    frame_data_.input_sample_rate_hz = test_config_->initial_sample_rate_hz;
    frame_data_.output_sample_rate_hz = test_config_->initial_sample_rate_hz;
  }

  // Prepare the audio data.
  StreamConfig input_stream_config(frame_data_.input_sample_rate_hz,
                                   frame_data_.input_number_of_channels);

  PopulateAudioFrame(kRenderInputFixLevel, input_stream_config.num_channels(),
                     input_stream_config.num_frames(), frame_data_.frame,
                     rand_gen_);

  PopulateAudioFrame(&frame_data_.input_frame[0], kRenderInputFloatLevel,
                     input_stream_config.num_channels(),
                     input_stream_config.num_frames(), rand_gen_);
}

// Makes the render side processing API call.
void RenderProcessor::CallApmRenderSide() {
  // Prepare a proper render side processing API call input.
  PrepareFrame();

  // Call the specified render side API processing method.
  StreamConfig input_stream_config(frame_data_.input_sample_rate_hz,
                                   frame_data_.input_number_of_channels);
  StreamConfig output_stream_config(frame_data_.output_sample_rate_hz,
                                    frame_data_.output_number_of_channels);
  int result = AudioProcessing::kNoError;
  switch (test_config_->render_api_function) {
    case RenderApiImpl::ProcessReverseStreamImplInteger:
      result = apm_->ProcessReverseStream(
          frame_data_.frame.data(), input_stream_config, output_stream_config,
          frame_data_.frame.data());
      break;
    case RenderApiImpl::ProcessReverseStreamImplFloat:
      result = apm_->ProcessReverseStream(
          &frame_data_.input_frame[0], input_stream_config,
          output_stream_config, &frame_data_.output_frame[0]);
      break;
    case RenderApiImpl::AnalyzeReverseStreamImplFloat:
      result = apm_->AnalyzeReverseStream(&frame_data_.input_frame[0],
                                          input_stream_config);
      break;
    default:
      FAIL();
  }

  // Check the return code for error.
  ASSERT_EQ(AudioProcessing::kNoError, result);
}

// Applies any render capture side APM API calls and audio stream
// characteristics
// specified by the scheme for the test.
void RenderProcessor::ApplyRuntimeSettingScheme() {
  const int render_count_local = frame_counters_->GetRenderCounter();

  // Update the number of channels and sample rates for the input and output.
  // Note that the counts frequencies for when to set parameters
  // are set using prime numbers in order to ensure that the
  // permutation scheme in the parameter setting changes.
  switch (test_config_->runtime_parameter_setting_scheme) {
    case RuntimeParameterSettingScheme::SparseStreamMetadataChangeScheme:
      if (render_count_local == 0)
        frame_data_.input_sample_rate_hz = 16000;
      else if (render_count_local % 47 == 0)
        frame_data_.input_sample_rate_hz = 32000;
      else if (render_count_local % 71 == 0)
        frame_data_.input_sample_rate_hz = 48000;
      else if (render_count_local % 79 == 0)
        frame_data_.input_sample_rate_hz = 16000;
      else if (render_count_local % 83 == 0)
        frame_data_.input_sample_rate_hz = 8000;

      if (render_count_local == 0)
        frame_data_.input_number_of_channels = 1;
      else if (render_count_local % 4 == 0)
        frame_data_.input_number_of_channels =
            (frame_data_.input_number_of_channels == 1 ? 2 : 1);

      if (render_count_local == 0)
        frame_data_.output_sample_rate_hz = 16000;
      else if (render_count_local % 17 == 0)
        frame_data_.output_sample_rate_hz = 32000;
      else if (render_count_local % 19 == 0)
        frame_data_.output_sample_rate_hz = 48000;
      else if (render_count_local % 29 == 0)
        frame_data_.output_sample_rate_hz = 16000;
      else if (render_count_local % 61 == 0)
        frame_data_.output_sample_rate_hz = 8000;

      if (render_count_local == 0)
        frame_data_.output_number_of_channels = 1;
      else if (render_count_local % 8 == 0)
        frame_data_.output_number_of_channels =
            (frame_data_.output_number_of_channels == 1 ? 2 : 1);
      break;
    case RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme:
      if (render_count_local == 0) {
        frame_data_.input_number_of_channels = 1;
        frame_data_.input_sample_rate_hz = 16000;
        frame_data_.output_number_of_channels = 1;
        frame_data_.output_sample_rate_hz = 16000;
      } else {
        frame_data_.input_number_of_channels =
            (frame_data_.input_number_of_channels == 1 ? 2 : 1);
        if (frame_data_.input_sample_rate_hz == 8000)
          frame_data_.input_sample_rate_hz = 16000;
        else if (frame_data_.input_sample_rate_hz == 16000)
          frame_data_.input_sample_rate_hz = 32000;
        else if (frame_data_.input_sample_rate_hz == 32000)
          frame_data_.input_sample_rate_hz = 48000;
        else if (frame_data_.input_sample_rate_hz == 48000)
          frame_data_.input_sample_rate_hz = 8000;

        frame_data_.output_number_of_channels =
            (frame_data_.output_number_of_channels == 1 ? 2 : 1);
        if (frame_data_.output_sample_rate_hz == 8000)
          frame_data_.output_sample_rate_hz = 16000;
        else if (frame_data_.output_sample_rate_hz == 16000)
          frame_data_.output_sample_rate_hz = 32000;
        else if (frame_data_.output_sample_rate_hz == 32000)
          frame_data_.output_sample_rate_hz = 48000;
        else if (frame_data_.output_sample_rate_hz == 48000)
          frame_data_.output_sample_rate_hz = 8000;
      }
      break;
    case RuntimeParameterSettingScheme::FixedMonoStreamMetadataScheme:
      if (render_count_local == 0) {
        frame_data_.input_sample_rate_hz = 16000;
        frame_data_.input_number_of_channels = 1;
        frame_data_.output_sample_rate_hz = 16000;
        frame_data_.output_number_of_channels = 1;
      }
      break;
    case RuntimeParameterSettingScheme::FixedStereoStreamMetadataScheme:
      if (render_count_local == 0) {
        frame_data_.input_sample_rate_hz = 16000;
        frame_data_.input_number_of_channels = 2;
        frame_data_.output_sample_rate_hz = 16000;
        frame_data_.output_number_of_channels = 2;
      }
      break;
    default:
      FAIL();
  }

  // Restric the number of output channels not to exceed
  // the number of input channels.
  frame_data_.output_number_of_channels =
      std::min(frame_data_.output_number_of_channels,
               frame_data_.input_number_of_channels);
}

}  // namespace

TEST_P(AudioProcessingImplLockTest, LockTest) {
  // Run test and verify that it did not time out.
  ASSERT_TRUE(RunTest());
}

// Instantiate tests from the extreme test configuration set.
INSTANTIATE_TEST_SUITE_P(
    DISABLED_AudioProcessingImplLockExtensive,
    AudioProcessingImplLockTest,
    ::testing::ValuesIn(TestConfig::GenerateExtensiveTestConfigs()));

INSTANTIATE_TEST_SUITE_P(
    AudioProcessingImplLockBrief,
    AudioProcessingImplLockTest,
    ::testing::ValuesIn(TestConfig::GenerateBriefTestConfigs()));

}  // namespace webrtc