chromium/media/audio/audio_encoders_unittest.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include <cstring>
#include <limits>
#include <memory>
#include <utility>
#include <vector>

#include "base/location.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/audio/audio_opus_encoder.h"
#include "media/audio/simple_sources.h"
#include "media/base/audio_encoder.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/converting_audio_fifo.h"
#include "media/base/status.h"
#include "media/media_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/opus/src/include/opus.h"

#if BUILDFLAG(IS_WIN)
#include "base/win/scoped_com_initializer.h"
#include "media/gpu/windows/mf_audio_encoder.h"

// The AAC tests are failing on Arm64. Disable the AAC part of these tests until
// those failures can be fixed. TODO(https://crbug.com/1424215): Fix tests,
// and/or investigate if AAC support should be turned off in Chrome for Arm64
// Windows, or if these are an issue with the tests.
#if !defined(ARCH_CPU_ARM64)
#define HAS_AAC_ENCODER
#endif

#endif  // IS_WIN

#if BUILDFLAG(IS_MAC) && BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/filters/mac/audio_toolbox_audio_encoder.h"
#define HAS_AAC_ENCODER
#endif

#if BUILDFLAG(IS_ANDROID) && BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/gpu/android/ndk_audio_encoder.h"
#define HAS_AAC_ENCODER
#endif

#if HAS_AAC_ENCODER
#include "media/base/audio_decoder.h"
#include "media/base/channel_layout.h"
#include "media/base/decoder_status.h"
#include "media/base/mock_media_log.h"
#include "media/filters/ffmpeg_audio_decoder.h"
#endif

namespace media {

namespace {

constexpr int kAudioSampleRateWithDelay =;

// This is the preferred opus buffer duration (20 ms), which corresponds to a
// value of 960 frames per buffer at a sample rate of 48 khz.
constexpr base::TimeDelta kOpusBufferDuration =;

#if HAS_AAC_ENCODER
// AAC puts 1024 PCM samples into each AAC frame, which corresponds to a
// duration of 21 and 1/3 milliseconds at a sample rate of 48 khz.
constexpr int kAacFramesPerBuffer = 1024;
#endif  //  HAS_AAC_ENCODER

struct TestAudioParams {};

constexpr TestAudioParams kTestAudioParamsOpus[] =;

#if HAS_AAC_ENCODER
constexpr TestAudioParams kTestAudioParamsAAC[] = {
    {AudioCodec::kAAC, 2, 48000}, {AudioCodec::kAAC, 6, 48000},
    {AudioCodec::kAAC, 1, 48000}, {AudioCodec::kAAC, 2, 44100},
    {AudioCodec::kAAC, 6, 44100}, {AudioCodec::kAAC, 1, 44100},
};
#endif  // HAS_AAC_ENCODER

std::string EncoderStatusCodeToString(EncoderStatus::Codes code) {}

bool TimesAreNear(base::TimeTicks t1,
                  base::TimeTicks t2,
                  base::TimeDelta error) {}

}  // namespace

class AudioEncodersTest : public ::testing::TestWithParam<TestAudioParams> {};

TEST_P(AudioEncodersTest, InitializeTwice) {}

TEST_P(AudioEncodersTest, StopCallbackWrapping) {}

TEST_P(AudioEncodersTest, EncodeWithoutInitialize) {}

TEST_P(AudioEncodersTest, FlushWithoutInitialize) {}

TEST_P(AudioEncodersTest, FlushWithNoInput) {}

TEST_P(AudioEncodersTest, EncodeAndFlush) {}

TEST_P(AudioEncodersTest, EncodeAndFlushTwice) {}

// Instead of synchronously calling `Encode`, wait until `done_cb` is invoked
// before we provide more input.
TEST_P(AudioEncodersTest, ProvideInputAfterDoneCb) {}

TEST_P(AudioEncodersTest, ManySmallInputs) {}

// Check that the encoder's timestamps don't drift from the expected timestamp
// over multiple inputs.
TEST_P(AudioEncodersTest, Timestamps) {}

// Check how the encoder reacts to breaks in continuity of incoming sound.
// Under normal circumstances capture times are expected to be exactly
// a buffer's duration apart, but if they are not, the encoder just ignores
// incoming capture times. In other words the only capture times that matter
// are
//   1. timestamp of the first encoded buffer
//   2. timestamps of buffers coming immediately after Flush() calls.
TEST_P(AudioEncodersTest, TimeContinuityBreak) {}

INSTANTIATE_TEST_SUITE_P();

#if HAS_AAC_ENCODER
INSTANTIATE_TEST_SUITE_P(AAC,
                         AudioEncodersTest,
                         testing::ValuesIn(kTestAudioParamsAAC));
#endif  // HAS_AAC_ENCODER

class AudioOpusEncoderTest : public AudioEncodersTest {};

TEST_P(AudioOpusEncoderTest, ExtraData) {}

TEST_P(AudioOpusEncoderTest, FullCycleEncodeDecode) {}

// Tests we can configure the AudioOpusEncoder's bitrate mode.
TEST_P(AudioOpusEncoderTest, FullCycleEncodeDecode_BitrateMode) {}

// Tests we can configure the AudioOpusEncoder's extra options.
TEST_P(AudioOpusEncoderTest, FullCycleEncodeDecode_OpusOptions) {}

TEST_P(AudioOpusEncoderTest, VariableChannelCounts) {}

INSTANTIATE_TEST_SUITE_P();

#if HAS_AAC_ENCODER
class AACAudioEncoderTest : public AudioEncodersTest {
 public:
  AACAudioEncoderTest() = default;
  AACAudioEncoderTest(const AACAudioEncoderTest&) = delete;
  AACAudioEncoderTest& operator=(const AACAudioEncoderTest&) = delete;
  ~AACAudioEncoderTest() override = default;

#if BUILDFLAG(ENABLE_FFMPEG) && BUILDFLAG(USE_PROPRIETARY_CODECS)
  void InitializeDecoder() {
    decoder_ = std::make_unique<FFmpegAudioDecoder>(
        base::SequencedTaskRunner::GetCurrentDefault(), &media_log);
    ChannelLayout channel_layout = CHANNEL_LAYOUT_NONE;
    switch (options_.channels) {
      case 1:
        channel_layout = CHANNEL_LAYOUT_MONO;
        break;
      case 2:
        channel_layout = CHANNEL_LAYOUT_STEREO;
        break;
      case 6:
        channel_layout = CHANNEL_LAYOUT_5_1_BACK;
        break;
      default:
        NOTREACHED_IN_MIGRATION();
    }
    AudioDecoderConfig config(AudioCodec::kAAC, SampleFormat::kSampleFormatS16,
                              channel_layout, options_.sample_rate,
                              /*extra_data=*/std::vector<uint8_t>(),
                              EncryptionScheme::kUnencrypted);
    auto init_cb = [](DecoderStatus decoder_status) {
      EXPECT_EQ(decoder_status, DecoderStatus::Codes::kOk);
    };
    auto output_cb = [&](scoped_refptr<AudioBuffer> decoded_buffer) {
      ++decoder_output_callback_count;
      EXPECT_EQ(decoded_buffer->frame_count(), frames_per_buffer_);
    };
    decoder_->Initialize(config, /*cdm_context=*/nullptr,
                         base::BindLambdaForTesting(init_cb),
                         base::BindLambdaForTesting(output_cb),
                         /*waiting_cb=*/base::DoNothing());
  }

 protected:
  MockMediaLog media_log;
  std::unique_ptr<FFmpegAudioDecoder> decoder_;
  int decoder_output_callback_count = 0;
#endif  // BUILDFLAG(ENABLE_FFMPEG) && BUILDFLAG(USE_PROPRIETARY_CODECS)
};

#if BUILDFLAG(IS_WIN)
// `MFAudioEncoder` requires `kMinSamplesForOutput` before `Flush` can be called
// successfully.
TEST_P(AACAudioEncoderTest, FlushWithTooLittleInput) {
  InitializeEncoder(base::DoNothing());
  ProduceAudioAndEncode();

  FlushAndVerifyStatus(EncoderStatus::Codes::kEncoderFailedFlush);

  ValidateDoneCallbacksRun();
}
#endif

#if BUILDFLAG(ENABLE_FFMPEG) && BUILDFLAG(USE_PROPRIETARY_CODECS)
TEST_P(AACAudioEncoderTest, FullCycleEncodeDecode) {
  InitializeDecoder();

  int encode_output_callback_count = 0;
  int decode_status_callback_count = 0;
  auto encode_output_cb = [&](EncodedAudioBuffer output, MaybeDesc) {
    ++encode_output_callback_count;

    auto decode_cb = [&](DecoderStatus status) {
      ++decode_status_callback_count;
      EXPECT_EQ(status, DecoderStatus::Codes::kOk);
    };
    scoped_refptr<DecoderBuffer> decoder_buffer =
        DecoderBuffer::FromArray(std::move(output.encoded_data));
    decoder_->Decode(decoder_buffer, base::BindLambdaForTesting(decode_cb));
  };

  InitializeEncoder(base::BindLambdaForTesting(encode_output_cb));

  ProduceAudioAndEncode();
  ProduceAudioAndEncode();
  ProduceAudioAndEncode();

  FlushAndVerifyStatus();

  // Let the decoder finish decoding.
  task_environment_.RunUntilIdle();

  int expected_outputs = 3 + std::ceil(GetExpectedPadding() /
                                       static_cast<double>(frames_per_buffer_));

  EXPECT_EQ(expected_outputs, encode_output_callback_count);
  EXPECT_EQ(expected_outputs, decode_status_callback_count);
  EXPECT_EQ(expected_outputs, decoder_output_callback_count);
}

TEST_P(AACAudioEncoderTest, FullCycleEncodeDecode_BitrateMode) {
  constexpr AudioEncoder::BitrateMode kTestAacBitrateMode[] = {
      AudioEncoder::BitrateMode::kConstant,
      AudioEncoder::BitrateMode::kVariable};

  for (const AudioEncoder::BitrateMode& bitrate_mode : kTestAacBitrateMode) {
    decoder_output_callback_count = 0;
    options_.bitrate_mode = bitrate_mode;

    // Recreate the encoder to pick up changes to `options_`.
    CreateEncoder();

    InitializeDecoder();

    auto encode_output_cb = [&](EncodedAudioBuffer output, MaybeDesc) {
      auto decode_cb = [&](DecoderStatus status) {
        EXPECT_EQ(status, DecoderStatus::Codes::kOk);
      };
      scoped_refptr<DecoderBuffer> decoder_buffer =
          DecoderBuffer::FromArray(std::move(output.encoded_data));
      decoder_->Decode(decoder_buffer, base::BindLambdaForTesting(decode_cb));
    };

    InitializeEncoder(base::BindLambdaForTesting(encode_output_cb));

    ProduceAudioAndEncode();
    ProduceAudioAndEncode();
    ProduceAudioAndEncode();

    FlushAndVerifyStatus();

    // Let the decoder finish decoding.
    task_environment_.RunUntilIdle();

    int expected_outputs =
        3 + std::ceil(GetExpectedPadding() /
                      static_cast<double>(frames_per_buffer_));

    EXPECT_EQ(expected_outputs, decoder_output_callback_count);
  }
}

// Makes sure we get extradata on the first output when we are using AAC output
// format, and no extradata when we are using ADTS output format.
TEST_P(AACAudioEncoderTest, AacOutputFormat) {
  constexpr AudioEncoder::AacOutputFormat kTestAacOutputFormat[] = {
      AudioEncoder::AacOutputFormat::AAC, AudioEncoder::AacOutputFormat::ADTS};

  for (const auto& output_format : kTestAacOutputFormat) {
    options_.aac = {output_format};

    // Recreate the encoder to pick up changes to `options_`.
    CreateEncoder();

    bool first_output = true;
    const bool needs_description =
        output_format == AudioEncoder::AacOutputFormat::AAC;

    auto encode_output_cb = [&](EncodedAudioBuffer output,
                                MaybeDesc codec_description) {
      if (first_output) {
        first_output = false;
        EXPECT_EQ(codec_description.has_value(), needs_description);
      } else {
        EXPECT_FALSE(codec_description);
      }
    };

    InitializeEncoder(base::BindLambdaForTesting(encode_output_cb));

    ProduceAudioAndEncode();
    ProduceAudioAndEncode();
    ProduceAudioAndEncode();

    FlushAndVerifyStatus();
  }
}
#endif  // BUILDFLAG(ENABLE_FFMPEG) && BUILDFLAG(USE_PROPRIETARY_CODECS)

INSTANTIATE_TEST_SUITE_P(AAC,
                         AACAudioEncoderTest,
                         testing::ValuesIn(kTestAudioParamsAAC));
#endif  // HAS_AAC_ENCODER

}  // namespace media