iSAC config: target bitrate exposed for fixed impl

It is now possible to set the target bitrate for iSAC for the fixed
point implementation. Unit tests added.

Bug: webrtc:11360
Change-Id: I60225d4ca1363cdacf18931e7cf412c5aec8d8fe
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/168529
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30526}
diff --git a/api/audio_codecs/isac/audio_encoder_isac_fix.cc b/api/audio_codecs/isac/audio_encoder_isac_fix.cc
index 7bcb78e..7cf55b9 100644
--- a/api/audio_codecs/isac/audio_encoder_isac_fix.cc
+++ b/api/audio_codecs/isac/audio_encoder_isac_fix.cc
@@ -56,6 +56,7 @@
   RTC_DCHECK(config.IsOk());
   AudioEncoderIsacFixImpl::Config c;
   c.frame_size_ms = config.frame_size_ms;
+  c.bit_rate = config.bit_rate;
   c.payload_type = payload_type;
   return std::make_unique<AudioEncoderIsacFixImpl>(c);
 }
diff --git a/api/audio_codecs/isac/audio_encoder_isac_fix.h b/api/audio_codecs/isac/audio_encoder_isac_fix.h
index 9bedde5..e50d9f5 100644
--- a/api/audio_codecs/isac/audio_encoder_isac_fix.h
+++ b/api/audio_codecs/isac/audio_encoder_isac_fix.h
@@ -26,8 +26,17 @@
 // parameter to CreateAudioEncoderFactory<...>().
 struct RTC_EXPORT AudioEncoderIsacFix {
   struct Config {
-    bool IsOk() const { return frame_size_ms == 30 || frame_size_ms == 60; }
+    bool IsOk() const {
+      if (frame_size_ms != 30 && frame_size_ms != 60) {
+        return false;
+      }
+      if (bit_rate < 10000 || bit_rate > 32000) {
+        return false;
+      }
+      return true;
+    }
     int frame_size_ms = 30;
+    int bit_rate = 32000;  // Limit on short-term average bit rate, in bits/s.
   };
   static absl::optional<Config> SdpToConfig(const SdpAudioFormat& audio_format);
   static void AppendSupportedEncoders(std::vector<AudioCodecSpec>* specs);
diff --git a/modules/audio_coding/codecs/isac/isac_webrtc_api_test.cc b/modules/audio_coding/codecs/isac/isac_webrtc_api_test.cc
index ac83861..3a2d6f4 100644
--- a/modules/audio_coding/codecs/isac/isac_webrtc_api_test.cc
+++ b/modules/audio_coding/codecs/isac/isac_webrtc_api_test.cc
@@ -12,22 +12,32 @@
 #include <limits>
 #include <vector>
 
+#include "absl/strings/string_view.h"
 #include "api/array_view.h"
 #include "api/audio_codecs/isac/audio_decoder_isac_fix.h"
 #include "api/audio_codecs/isac/audio_decoder_isac_float.h"
 #include "api/audio_codecs/isac/audio_encoder_isac_fix.h"
 #include "api/audio_codecs/isac/audio_encoder_isac_float.h"
 #include "rtc_base/random.h"
+#include "rtc_base/strings/string_builder.h"
 #include "test/gtest.h"
 
 namespace webrtc {
 namespace {
 
 constexpr int kPayloadType = 42;
-constexpr int kBitrateBps = 20000;
 
 enum class IsacImpl { kFixed, kFloat };
 
+absl::string_view IsacImplToString(IsacImpl impl) {
+  switch (impl) {
+    case IsacImpl::kFixed:
+      return "fixed";
+    case IsacImpl::kFloat:
+      return "float";
+  }
+}
+
 std::vector<int16_t> GetRandomSamplesVector(size_t size) {
   constexpr int32_t kMin = std::numeric_limits<int16_t>::min();
   constexpr int32_t kMax = std::numeric_limits<int16_t>::max();
@@ -39,67 +49,210 @@
   return v;
 }
 
-class IsacApiTest
-    : public testing::TestWithParam<std::tuple<int, int, IsacImpl, IsacImpl>> {
+std::unique_ptr<AudioEncoder> CreateEncoder(IsacImpl impl,
+                                            int sample_rate_hz,
+                                            int frame_size_ms,
+                                            int bitrate_bps) {
+  RTC_CHECK(sample_rate_hz == 16000 || sample_rate_hz == 32000);
+  RTC_CHECK(frame_size_ms == 30 || frame_size_ms == 60);
+  RTC_CHECK_GT(bitrate_bps, 0);
+  switch (impl) {
+    case IsacImpl::kFixed: {
+      AudioEncoderIsacFix::Config config;
+      config.bit_rate = bitrate_bps;
+      config.frame_size_ms = frame_size_ms;
+      RTC_CHECK_EQ(16000, sample_rate_hz);
+      return AudioEncoderIsacFix::MakeAudioEncoder(config, kPayloadType);
+    }
+    case IsacImpl::kFloat: {
+      AudioEncoderIsacFloat::Config config;
+      config.bit_rate = bitrate_bps;
+      config.frame_size_ms = frame_size_ms;
+      config.sample_rate_hz = sample_rate_hz;
+      return AudioEncoderIsacFloat::MakeAudioEncoder(config, kPayloadType);
+    }
+  }
+}
+
+std::unique_ptr<AudioDecoder> CreateDecoder(IsacImpl impl, int sample_rate_hz) {
+  RTC_CHECK(sample_rate_hz == 16000 || sample_rate_hz == 32000);
+  switch (impl) {
+    case IsacImpl::kFixed: {
+      webrtc::AudioDecoderIsacFix::Config config;
+      RTC_CHECK_EQ(16000, sample_rate_hz);
+      return webrtc::AudioDecoderIsacFix::MakeAudioDecoder(config);
+    }
+    case IsacImpl::kFloat: {
+      webrtc::AudioDecoderIsacFloat::Config config;
+      config.sample_rate_hz = sample_rate_hz;
+      return webrtc::AudioDecoderIsacFloat::MakeAudioDecoder(config);
+    }
+  }
+}
+
+struct EncoderTestParams {
+  IsacImpl impl;
+  int sample_rate_hz;
+  int frame_size_ms;
+};
+
+class EncoderTest : public testing::TestWithParam<EncoderTestParams> {
  protected:
-  IsacApiTest() : input_frame_(GetRandomSamplesVector(GetInputFrameLength())) {}
+  EncoderTest() = default;
+  IsacImpl GetIsacImpl() const { return GetParam().impl; }
+  int GetSampleRateHz() const { return GetParam().sample_rate_hz; }
+  int GetFrameSizeMs() const { return GetParam().frame_size_ms; }
+};
+
+TEST_P(EncoderTest, TestConfig) {
+  for (int bitrate_bps : {10000, 21000, 32000}) {
+    SCOPED_TRACE(bitrate_bps);
+    auto encoder = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
+                                 GetFrameSizeMs(), bitrate_bps);
+    EXPECT_EQ(GetSampleRateHz(), encoder->SampleRateHz());
+    EXPECT_EQ(size_t{1}, encoder->NumChannels());
+    EXPECT_EQ(bitrate_bps, encoder->GetTargetBitrate());
+  }
+}
+
+// Encodes an input audio sequence with a low and a high target bitrate and
+// checks that the number of produces bytes in the first case is less than that
+// of the second case.
+TEST_P(EncoderTest, TestDifferentBitrates) {
+  constexpr int kLowBps = 20000;
+  constexpr int kHighBps = 25000;
+  auto encoder_low = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
+                                   GetFrameSizeMs(), kLowBps);
+  auto encoder_high = CreateEncoder(GetIsacImpl(), GetSampleRateHz(),
+                                    GetFrameSizeMs(), kHighBps);
+  int num_bytes_low = 0;
+  int num_bytes_high = 0;
+  const auto in = GetRandomSamplesVector(
+      /*size=*/rtc::CheckedDivExact(GetSampleRateHz(), 100));
+  constexpr int kNumFrames = 12;
+  for (int i = 0; i < kNumFrames; ++i) {
+    rtc::Buffer low, high;
+    encoder_low->Encode(/*rtp_timestamp=*/0, in, &low);
+    encoder_high->Encode(/*rtp_timestamp=*/0, in, &high);
+    num_bytes_low += low.size();
+    num_bytes_high += high.size();
+  }
+  EXPECT_LT(num_bytes_low, num_bytes_high);
+}
+
+// Checks that the target and the measured bitrates are within tolerance.
+// TODO(webrtc:11360): Add CBR flag to the config and re-enable test with CBR.
+TEST_P(EncoderTest, DISABLED_TestBitrateNearTarget) {
+  const auto in = GetRandomSamplesVector(
+      /*size=*/rtc::CheckedDivExact(GetSampleRateHz(), 100));  // 10 ms.
+  for (int bitrate_bps : {10000, 15000, 20000, 26000, 32000}) {
+    SCOPED_TRACE(bitrate_bps);
+    auto e = CreateEncoder(GetIsacImpl(), GetSampleRateHz(), GetFrameSizeMs(),
+                           bitrate_bps);
+    int num_bytes = 0;
+    constexpr int kNumFrames = 60;
+    for (int i = 0; i < kNumFrames; ++i) {
+      rtc::Buffer encoded;
+      e->Encode(/*rtp_timestamp=*/0, in, &encoded);
+      num_bytes += encoded.size();
+    }
+    // Inverse of the duration of |kNumFrames| 10 ms frames (unit: seconds^-1).
+    constexpr float kAudioDurationInv = 100.f / kNumFrames;
+    const int measured_bitrate_bps = 8 * num_bytes * kAudioDurationInv;
+    EXPECT_NEAR(bitrate_bps, measured_bitrate_bps, 1000);  // Max 1 kbps.
+  }
+}
+
+// Creates tests for different encoder configurations and implementations.
+INSTANTIATE_TEST_SUITE_P(
+    IsacApiTest,
+    EncoderTest,
+    ::testing::ValuesIn([] {
+      std::vector<EncoderTestParams> cases;
+      for (IsacImpl impl : {IsacImpl::kFloat, IsacImpl::kFixed}) {
+        for (int frame_size_ms : {30, 60}) {
+          cases.push_back({impl, 16000, frame_size_ms});
+        }
+      }
+      cases.push_back({IsacImpl::kFloat, 32000, 30});
+      return cases;
+    }()),
+    [](const ::testing::TestParamInfo<EncoderTestParams>& info) {
+      rtc::StringBuilder b;
+      const auto& p = info.param;
+      b << IsacImplToString(p.impl) << "_" << p.sample_rate_hz << "_"
+        << p.frame_size_ms;
+      return b.Release();
+    });
+
+struct DecoderTestParams {
+  IsacImpl impl;
+  int sample_rate_hz;
+};
+
+class DecoderTest : public testing::TestWithParam<DecoderTestParams> {
+ protected:
+  DecoderTest() = default;
+  IsacImpl GetIsacImpl() const { return GetParam().impl; }
+  int GetSampleRateHz() const { return GetParam().sample_rate_hz; }
+};
+
+TEST_P(DecoderTest, TestConfig) {
+  auto decoder = CreateDecoder(GetIsacImpl(), GetSampleRateHz());
+  EXPECT_EQ(GetSampleRateHz(), decoder->SampleRateHz());
+  EXPECT_EQ(size_t{1}, decoder->Channels());
+}
+
+// Creates tests for different decoder configurations and implementations.
+INSTANTIATE_TEST_SUITE_P(
+    IsacApiTest,
+    DecoderTest,
+    ::testing::ValuesIn({DecoderTestParams{IsacImpl::kFixed, 16000},
+                         DecoderTestParams{IsacImpl::kFloat, 16000},
+                         DecoderTestParams{IsacImpl::kFloat, 32000}}),
+    [](const ::testing::TestParamInfo<DecoderTestParams>& info) {
+      const auto& p = info.param;
+      return (rtc::StringBuilder()
+              << IsacImplToString(p.impl) << "_" << p.sample_rate_hz)
+          .Release();
+    });
+
+struct EncoderDecoderPairTestParams {
+  int sample_rate_hz;
+  int frame_size_ms;
+  IsacImpl encoder_impl;
+  IsacImpl decoder_impl;
+};
+
+class EncoderDecoderPairTest
+    : public testing::TestWithParam<EncoderDecoderPairTestParams> {
+ protected:
+  EncoderDecoderPairTest()
+      : input_frame_(GetRandomSamplesVector(GetInputFrameSize())) {}
   rtc::ArrayView<const int16_t> GetInputFrame() { return input_frame_; }
-  int GetSampleRateHz() const { return std::get<0>(GetParam()); }
-  int GetEncoderFrameLenght() const {
-    return GetEncoderFrameLenghtMs() * GetSampleRateHz() / 1000;
-  }
-  std::unique_ptr<AudioEncoder> CreateEncoder() const {
-    switch (GetEncoderIsacImpl()) {
-      case IsacImpl::kFixed: {
-        AudioEncoderIsacFix::Config config;
-        config.frame_size_ms = GetEncoderFrameLenghtMs();
-        RTC_CHECK_EQ(16000, GetSampleRateHz());
-        return AudioEncoderIsacFix::MakeAudioEncoder(config, kPayloadType);
-      }
-      case IsacImpl::kFloat: {
-        AudioEncoderIsacFloat::Config config;
-        config.bit_rate = kBitrateBps;
-        config.frame_size_ms = GetEncoderFrameLenghtMs();
-        config.sample_rate_hz = GetSampleRateHz();
-        return AudioEncoderIsacFloat::MakeAudioEncoder(config, kPayloadType);
-      }
-    }
-  }
-  std::unique_ptr<AudioDecoder> CreateDecoder() const {
-    switch (GetDecoderIsacImpl()) {
-      case IsacImpl::kFixed: {
-        webrtc::AudioDecoderIsacFix::Config config;
-        RTC_CHECK_EQ(16000, GetSampleRateHz());
-        return webrtc::AudioDecoderIsacFix::MakeAudioDecoder(config);
-      }
-      case IsacImpl::kFloat: {
-        webrtc::AudioDecoderIsacFloat::Config config;
-        config.sample_rate_hz = GetSampleRateHz();
-        return webrtc::AudioDecoderIsacFloat::MakeAudioDecoder(config);
-      }
-    }
+  int GetSampleRateHz() const { return GetParam().sample_rate_hz; }
+  int GetEncoderFrameSizeMs() const { return GetParam().frame_size_ms; }
+  IsacImpl GetEncoderIsacImpl() const { return GetParam().encoder_impl; }
+  IsacImpl GetDecoderIsacImpl() const { return GetParam().decoder_impl; }
+
+  int GetEncoderFrameSize() const {
+    return GetEncoderFrameSizeMs() * GetSampleRateHz() / 1000;
   }
 
  private:
   const std::vector<int16_t> input_frame_;
-  int GetInputFrameLength() const {
-    return rtc::CheckedDivExact(std::get<0>(GetParam()), 100);  // 10 ms.
+  int GetInputFrameSize() const {
+    return rtc::CheckedDivExact(GetParam().sample_rate_hz, 100);  // 10 ms.
   }
-  int GetEncoderFrameLenghtMs() const {
-    int frame_size_ms = std::get<1>(GetParam());
-    RTC_CHECK(frame_size_ms == 30 || frame_size_ms == 60);
-    return frame_size_ms;
-  }
-  IsacImpl GetEncoderIsacImpl() const { return std::get<2>(GetParam()); }
-  IsacImpl GetDecoderIsacImpl() const { return std::get<3>(GetParam()); }
 };
 
 // Checks that the number of encoded and decoded samples match.
-TEST_P(IsacApiTest, EncodeDecode) {
-  auto encoder = CreateEncoder();
-  auto decoder = CreateDecoder();
-  const int encoder_frame_length = GetEncoderFrameLenght();
-  std::vector<int16_t> out(encoder_frame_length);
+TEST_P(EncoderDecoderPairTest, EncodeDecode) {
+  auto encoder = CreateEncoder(GetEncoderIsacImpl(), GetSampleRateHz(),
+                               GetEncoderFrameSizeMs(), /*bitrate_bps=*/20000);
+  auto decoder = CreateDecoder(GetDecoderIsacImpl(), GetSampleRateHz());
+  const int encoder_frame_size = GetEncoderFrameSize();
+  std::vector<int16_t> out(encoder_frame_size);
   size_t num_encoded_samples = 0;
   size_t num_decoded_samples = 0;
   constexpr int kNumFrames = 12;
@@ -123,23 +276,31 @@
   EXPECT_EQ(num_encoded_samples, num_decoded_samples);
 }
 
-// Creates tests for different encoder frame lengths and different
+// Creates tests for different encoder frame sizes and different
 // encoder/decoder implementations.
 INSTANTIATE_TEST_SUITE_P(
-    AllTest,
     IsacApiTest,
+    EncoderDecoderPairTest,
     ::testing::ValuesIn([] {
-      std::vector<std::tuple<int, int, IsacImpl, IsacImpl>> cases;
-      for (int frame_length_ms : {30, 60}) {
+      std::vector<EncoderDecoderPairTestParams> cases;
+      for (int frame_size_ms : {30, 60}) {
         for (IsacImpl enc : {IsacImpl::kFloat, IsacImpl::kFixed}) {
           for (IsacImpl dec : {IsacImpl::kFloat, IsacImpl::kFixed}) {
-            cases.push_back({16000, frame_length_ms, enc, dec});
+            cases.push_back({16000, frame_size_ms, enc, dec});
           }
         }
       }
       cases.push_back({32000, 30, IsacImpl::kFloat, IsacImpl::kFloat});
       return cases;
-    }()));
+    }()),
+    [](const ::testing::TestParamInfo<EncoderDecoderPairTestParams>& info) {
+      rtc::StringBuilder b;
+      const auto& p = info.param;
+      b << p.sample_rate_hz << "_" << p.frame_size_ms << "_"
+        << IsacImplToString(p.encoder_impl) << "_"
+        << IsacImplToString(p.decoder_impl);
+      return b.Release();
+    });
 
 }  // namespace
 }  // namespace webrtc