text: // Copyright 2017 The Chromium OS Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <gtest/gtest.h>
  #include <stdint.h>
  #include <stdio.h>
  #include <time.h>
  
  #include <memory>
  #include <unordered_map>
  
  extern "C" {
  #include "cras_iodev.h"    // stubbed
  #include "cras_rstream.h"  // stubbed
  #include "cras_shm.h"
  #include "cras_types.h"
  #include "dev_io.h"      // tested
  #include "dev_stream.h"  // stubbed
  #include "utlist.h"
  
  struct audio_thread_event_log* atlog;
  }
  
  #include "dev_io_stubs.h"
  #include "iodev_stub.h"
  #include "metrics_stub.h"
  #include "rstream_stub.h"
  
  static float dev_stream_capture_software_gain_scaler_val;
  static float input_data_get_software_gain_scaler_val;
  static unsigned int dev_stream_capture_avail_ret = 480;
  struct set_dev_rate_data {
    unsigned int dev_rate;
    double dev_rate_ratio;
    double main_rate_ratio;
    int coarse_rate_adjust;
  };
  std::unordered_map<struct dev_stream*, set_dev_rate_data> set_dev_rate_map;
  
  namespace {
  
  class DevIoSuite : public testing::Test {
   protected:
    virtual void SetUp() {
      atlog = static_cast<audio_thread_event_log*>(calloc(1, sizeof(*atlog)));
      iodev_stub_reset();
      rstream_stub_reset();
      fill_audio_format(&format, 48000);
      set_dev_rate_map.clear();
      stream = create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format);
    }
  
    virtual void TearDown() { free(atlog); }
  
    size_t cb_threshold = 480;
    cras_audio_format format;
    StreamPtr stream;
  };
  
  TEST_F(DevIoSuite, SendCapturedFails) {
    // rstream's next callback is now and there is enough data to fill.
    struct timespec start;
    clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    stream->rstream->next_cb_ts = start;
    AddFakeDataToStream(stream.get(), 480);
  
    struct open_dev* dev_list = NULL;
    DevicePtr dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format,
                                  CRAS_NODE_TYPE_MIC);
    DL_APPEND(dev_list, dev->odev.get());
    add_stream_to_dev(dev->dev, stream);
  
    // Set failure response from frames_queued.
    iodev_stub_frames_queued(dev->dev.get(), -3, start);
  
    EXPECT_EQ(-3, dev_io_send_captured_samples(dev_list));
  }
  
  TEST_F(DevIoSuite, CaptureGain) {
    struct open_dev* dev_list = NULL;
    struct open_dev* odev_list = NULL;
    struct timespec ts;
    DevicePtr dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format,
                                  CRAS_NODE_TYPE_MIC);
  
    dev->dev->state = CRAS_IODEV_STATE_NORMAL_RUN;
    iodev_stub_frames_queued(dev->dev.get(), 20, ts);
    DL_APPEND(dev_list, dev->odev.get());
    add_stream_to_dev(dev->dev, stream);
  
    /* The applied scaler gain should match what is reported by input_data. */
    dev->dev->active_node->ui_gain_scaler = 1.0f;
    input_data_get_software_gain_scaler_val = 1.0f;
    dev_io_capture(&dev_list, &odev_list);
    EXPECT_EQ(1.0f, dev_stream_capture_software_gain_scaler_val);
  
    input_data_get_software_gain_scaler_val = 0.99f;
    dev_io_capture(&dev_list, &odev_list);
    EXPECT_EQ(0.99f, dev_stream_capture_software_gain_scaler_val);
  
    dev->dev->active_node->ui_gain_scaler = 0.6f;
    input_data_get_software_gain_scaler_val = 0.7f;
    dev_io_capture(&dev_list, &odev_list);
    EXPECT_FLOAT_EQ(0.42f, dev_stream_capture_software_gain_scaler_val);
  }
  
  /*
   * When input and output devices are on the internal sound card,
   * and their device rates are the same, use the estimated rate
   * on the output device as the estimated rate of input device.
   */
  TEST_F(DevIoSuite, CopyOutputEstimatedRate) {
    struct open_dev* idev_list = NULL;
    struct open_dev* odev_list = NULL;
    struct timespec ts;
    DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, cb_threshold, &format,
                                      CRAS_NODE_TYPE_INTERNAL_SPEAKER);
    DevicePtr in_dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format,
                                     CRAS_NODE_TYPE_MIC);
  
    in_dev->dev->state = CRAS_IODEV_STATE_NORMAL_RUN;
    iodev_stub_frames_queued(in_dev->dev.get(), 20, ts);
    DL_APPEND(idev_list, in_dev->odev.get());
    add_stream_to_dev(in_dev->dev, stream);
    DL_APPEND(odev_list, out_dev->odev.get());
    iodev_stub_on_internal_card(out_dev->dev->active_node, 1);
    iodev_stub_on_internal_card(in_dev->dev->active_node, 1);
  
    iodev_stub_est_rate_ratio(in_dev->dev.get(), 0.8f);
    iodev_stub_est_rate_ratio(out_dev->dev.get(), 1.2f);
  
    dev_io_capture(&idev_list, &odev_list);
  
    EXPECT_FLOAT_EQ(1.2f, set_dev_rate_map[stream->dstream.get()].dev_rate_ratio);
  }
  
  /*
   * When input and output devices are not both on the internal sound card,
   * estimated rates are independent.
   */
  TEST_F(DevIoSuite, InputOutputIndependentEstimatedRate) {
    struct open_dev* idev_list = NULL;
    struct open_dev* odev_list = NULL;
    struct timespec ts;
    DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, cb_threshold, &format,
                                      CRAS_NODE_TYPE_INTERNAL_SPEAKER);
    DevicePtr in_dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format,
                                     CRAS_NODE_TYPE_USB);
  
    in_dev->dev->state = CRAS_IODEV_STATE_NORMAL_RUN;
    iodev_stub_frames_queued(in_dev->dev.get(), 20, ts);
    DL_APPEND(idev_list, in_dev->odev.get());
    add_stream_to_dev(in_dev->dev, stream);
    DL_APPEND(odev_list, out_dev->odev.get());
    iodev_stub_on_internal_card(out_dev->dev->active_node, 1);
    iodev_stub_on_internal_card(in_dev->dev->active_node, 0);
  
    iodev_stub_est_rate_ratio(in_dev->dev.get(), 0.8f);
    iodev_stub_est_rate_ratio(out_dev->dev.get(), 1.2f);
    iodev_stub_update_rate(in_dev->dev.get(), 1);
  
    dev_io_capture(&idev_list, &odev_list);
  
    EXPECT_FLOAT_EQ(0.8f, set_dev_rate_map[stream->dstream.get()].dev_rate_ratio);
  }
  
  /*
   * If any hw_level is larger than 1.5 * largest_cb_level and
   * DROP_FRAMES_THRESHOLD_MS, reset all input devices.
   */
  TEST_F(DevIoSuite, SendCapturedNeedToResetDevices) {
    struct timespec start;
    struct timespec drop_time;
    struct open_dev* dev_list = NULL;
    bool rc;
  
    clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    AddFakeDataToStream(stream.get(), 0);
  
    DevicePtr dev1 =
        create_device(CRAS_STREAM_INPUT, 1000, &format, CRAS_NODE_TYPE_MIC);
    DevicePtr dev2 =
        create_device(CRAS_STREAM_INPUT, 10000, &format, CRAS_NODE_TYPE_MIC);
    DL_APPEND(dev_list, dev1->odev.get());
    DL_APPEND(dev_list, dev2->odev.get());
    add_stream_to_dev(dev1->dev, stream);
    add_stream_to_dev(dev2->dev, stream);
  
    iodev_stub_frames_queued(dev1->dev.get(), 2880, start);
    iodev_stub_frames_queued(dev2->dev.get(), 4800, start);
    EXPECT_EQ(0, dev_io_send_captured_samples(dev_list));
  
    /*
     * Should drop frames to one min_cb_level, which is MIN(2880, 4800) - 480 =
     * 2400 (50ms).
     */
    rc = iodev_stub_get_drop_time(dev1->dev.get(), &drop_time);
    EXPECT_EQ(true, rc);
    EXPECT_EQ(0, drop_time.tv_sec);
    EXPECT_EQ(50000000, drop_time.tv_nsec);
  
    rc = iodev_stub_get_drop_time(dev2->dev.get(), &drop_time);
    EXPECT_EQ(true, rc);
    EXPECT_EQ(0, drop_time.tv_sec);
    EXPECT_EQ(50000000, drop_time.tv_nsec);
  }
  
  /*
   * If any hw_level is larger than 0.5 * buffer_size and
   * DROP_FRAMES_THRESHOLD_MS, reset all input devices.
   */
  
  TEST_F(DevIoSuite, SendCapturedNeedToResetDevices2) {
    struct timespec start;
    struct timespec drop_time;
    struct open_dev* dev_list = NULL;
    bool rc;
  
    stream = create_stream(1, 1, CRAS_STREAM_INPUT, 2000, &format);
  
    clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    AddFakeDataToStream(stream.get(), 0);
  
    DevicePtr dev1 =
        create_device(CRAS_STREAM_INPUT, 2048, &format, CRAS_NODE_TYPE_MIC);
    DevicePtr dev2 =
        create_device(CRAS_STREAM_INPUT, 10000, &format, CRAS_NODE_TYPE_MIC);
    DL_APPEND(dev_list, dev1->odev.get());
    DL_APPEND(dev_list, dev2->odev.get());
    add_stream_to_dev(dev1->dev, stream);
    add_stream_to_dev(dev2->dev, stream);
  
    iodev_stub_frames_queued(dev1->dev.get(), 2480, start);
    iodev_stub_frames_queued(dev2->dev.get(), 2480, start);
    EXPECT_EQ(0, dev_io_send_captured_samples(dev_list));
  
    /*
     * Should drop frames to one min_cb_level, which is 2480 - 2000 = 480 (10ms).
     */
    rc = iodev_stub_get_drop_time(dev1->dev.get(), &drop_time);
    EXPECT_EQ(true, rc);
    EXPECT_EQ(0, drop_time.tv_sec);
    EXPECT_EQ(10000000, drop_time.tv_nsec);
  
    rc = iodev_stub_get_drop_time(dev2->dev.get(), &drop_time);
    EXPECT_EQ(true, rc);
    EXPECT_EQ(0, drop_time.tv_sec);
    EXPECT_EQ(10000000, drop_time.tv_nsec);
  }
  
  /*
   * If the hw_level is larger than 1.5 * largest_cb_level but less than
   * DROP_FRAMES_THRESHOLD_MS, do nothing.
   */
  TEST_F(DevIoSuite, SendCapturedLevelLessThanThreshold) {
    struct timespec start;
    struct timespec drop_time;
    struct open_dev* dev_list = NULL;
    bool rc;
  
    clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    AddFakeDataToStream(stream.get(), 0);
  
    DevicePtr dev =
        create_device(CRAS_STREAM_INPUT, 480, &format, CRAS_NODE_TYPE_MIC);
    DL_APPEND(dev_list, dev->odev.get());
    add_stream_to_dev(dev->dev, stream);
  
    iodev_stub_frames_queued(dev->dev.get(), 2048, start);
    EXPECT_EQ(0, dev_io_send_captured_samples(dev_list));
  
    rc = iodev_stub_get_drop_time(dev->dev.get(), &drop_time);
    EXPECT_EQ(false, rc);
  }
  
  /*
   * If all hw_level is less than 1.5 * largest_cb_level and 0.5 * buffer_size,
   * do nothing.
   */
  TEST_F(DevIoSuite, SendCapturedNoNeedToResetDevices) {
    struct timespec start;
    struct timespec drop_time;
    struct open_dev* dev_list = NULL;
    bool rc;
  
    clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    AddFakeDataToStream(stream.get(), 0);
  
    DevicePtr dev1 =
        create_device(CRAS_STREAM_INPUT, 1000, &format, CRAS_NODE_TYPE_MIC);
    DevicePtr dev2 =
        create_device(CRAS_STREAM_INPUT, 10000, &format, CRAS_NODE_TYPE_MIC);
    DL_APPEND(dev_list, dev1->odev.get());
    DL_APPEND(dev_list, dev2->odev.get());
    add_stream_to_dev(dev1->dev, stream);
    add_stream_to_dev(dev2->dev, stream);
  
    iodev_stub_frames_queued(dev1->dev.get(), 400, start);
    iodev_stub_frames_queued(dev2->dev.get(), 400, start);
    EXPECT_EQ(0, dev_io_send_captured_samples(dev_list));
  
    rc = iodev_stub_get_drop_time(dev1->dev.get(), &drop_time);
    EXPECT_EQ(false, rc);
  
    rc = iodev_stub_get_drop_time(dev2->dev.get(), &drop_time);
    EXPECT_EQ(false, rc);
  }
  
  /*
   * On loopback and hotword devices, if any hw_level is larger than
   *  1.5 * largest_cb_level and DROP_FRAMES_THRESHOLD_MS, do nothing.
   */
  TEST_F(DevIoSuite, SendCapturedNoNeedToDrop) {
    struct timespec start;
    struct timespec drop_time;
    struct open_dev* dev_list = NULL;
    bool rc;
  
    clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    AddFakeDataToStream(stream.get(), 0);
  
    DevicePtr dev1 =
        create_device(CRAS_STREAM_INPUT, 480, &format, CRAS_NODE_TYPE_HOTWORD);
    DevicePtr dev2 = create_device(CRAS_STREAM_INPUT, 480, &format,
                                   CRAS_NODE_TYPE_POST_MIX_PRE_DSP);
    DevicePtr dev3 =
        create_device(CRAS_STREAM_INPUT, 480, &format, CRAS_NODE_TYPE_POST_DSP);
  
    DL_APPEND(dev_list, dev1->odev.get());
    DL_APPEND(dev_list, dev2->odev.get());
    DL_APPEND(dev_list, dev3->odev.get());
  
    add_stream_to_dev(dev1->dev, stream);
    add_stream_to_dev(dev2->dev, stream);
    add_stream_to_dev(dev3->dev, stream);
  
    iodev_stub_frames_queued(dev1->dev.get(), 4800, start);
    iodev_stub_frames_queued(dev2->dev.get(), 4800, start);
    iodev_stub_frames_queued(dev2->dev.get(), 4800, start);
  
    EXPECT_EQ(0, dev_io_send_captured_samples(dev_list));
  
    rc = iodev_stub_get_drop_time(dev1->dev.get(), &drop_time);
    EXPECT_EQ(false, rc);
  
    rc = iodev_stub_get_drop_time(dev2->dev.get(), &drop_time);
    EXPECT_EQ(false, rc);
  
    rc = iodev_stub_get_drop_time(dev3->dev.get(), &drop_time);
    EXPECT_EQ(false, rc);
  }
  
  /* Stubs */
  extern "C" {
  
  int input_data_get_for_stream(struct input_data* data,
                                struct cras_rstream* stream,
                                struct buffer_share* offsets,
                                struct cras_audio_area** area,
                                unsigned int* offset) {
    return 0;
  }
  
  int input_data_put_for_stream(struct input_data* data,
                                struct cras_rstream* stream,
                                struct buffer_share* offsets,
                                unsigned int frames) {
    return 0;
  }
  
  float input_data_get_software_gain_scaler(struct input_data* data,
                                            float idev_sw_gain_scaler,
                                            struct cras_rstream* stream) {
    return input_data_get_software_gain_scaler_val;
  }
  
  int cras_audio_thread_event_drop_samples() {
    return 0;
  }
  
  int cras_audio_thread_event_severe_underrun() {
    return 0;
  }
  
  int dev_stream_attached_devs(const struct dev_stream* dev_stream) {
    return 0;
  }
  void dev_stream_update_frames(const struct dev_stream* dev_stream) {}
  int dev_stream_playback_frames(const struct dev_stream* dev_stream) {
    return 0;
  }
  int dev_stream_is_pending_reply(const struct dev_stream* dev_stream) {
    return 0;
  }
  int dev_stream_mix(struct dev_stream* dev_stream,
                     const struct cras_audio_format* fmt,
                     uint8_t* dst,
                     unsigned int num_to_write) {
    return 0;
  }
  void dev_stream_set_dev_rate(struct dev_stream* dev_stream,
                               unsigned int dev_rate,
                               double dev_rate_ratio,
                               double main_rate_ratio,
                               int coarse_rate_adjust) {
    set_dev_rate_data new_data;
    new_data.dev_rate = dev_rate;
    new_data.dev_rate_ratio = dev_rate_ratio;
    new_data.main_rate_ratio = main_rate_ratio;
    new_data.coarse_rate_adjust = coarse_rate_adjust;
  
    set_dev_rate_map[dev_stream] = new_data;
  }
  int dev_stream_capture_update_rstream(struct dev_stream* dev_stream) {
    return 0;
  }
  int dev_stream_wake_time(struct dev_stream* dev_stream,
                           unsigned int curr_level,
                           struct timespec* level_tstamp,
                           unsigned int cap_limit,
                           int is_cap_limit_stream,
                           struct timespec* wake_time_out) {
    return 0;
  }
  int dev_stream_flush_old_audio_messages(struct dev_stream* dev_stream) {
    return 0;
  }
  void dev_stream_set_delay(const struct dev_stream* dev_stream,
                            unsigned int delay_frames) {}
  unsigned int dev_stream_capture(struct dev_stream* dev_stream,
                                  const struct cras_audio_area* area,
                                  unsigned int area_offset,
                                  float software_gain_scaler) {
    dev_stream_capture_software_gain_scaler_val = software_gain_scaler;
    return 0;
  }
  void dev_stream_update_next_wake_time(struct dev_stream* dev_stream) {}
  int dev_stream_request_playback_samples(struct dev_stream* dev_stream,
                                          const struct timespec* now) {
    return 0;
  }
  int dev_stream_playback_update_rstream(struct dev_stream* dev_stream) {
    return 0;
  }
  void dev_stream_destroy(struct dev_stream* dev_stream) {}
  unsigned int dev_stream_capture_avail(const struct dev_stream* dev_stream) {
    return dev_stream_capture_avail_ret;
  }
  struct dev_stream* dev_stream_create(struct cras_rstream* stream,
                                       unsigned int dev_id,
                                       const struct cras_audio_format* dev_fmt,
                                       void* dev_ptr,
                                       struct timespec* cb_ts,
                                       const struct timespec* sleep_interval_ts) {
    return 0;
  }
  int cras_device_monitor_error_close(unsigned int dev_idx) {
    return 0;
  }
  }  // extern "C"
  
  }  //  namespace
  
  int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
  }
  