#include "media/audio/apple/audio_low_latency_input.h"
#include <stdint.h>
#include <memory>
#include "base/compiler_specific.h"
#include "base/containers/heap_array.h"
#include "base/environment.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_device_info_accessor_for_tests.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_manager_base.h"
#include "media/audio/audio_unittest_util.h"
#include "media/audio/test_audio_thread.h"
#include "media/base/audio_glitch_info.h"
#include "media/base/audio_sample_types.h"
#include "media/base/seekable_buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::Ge;
using ::testing::NotNull;
namespace media {
ACTION_P4(CheckCountAndPostQuitTask, count, limit, task_runner, closure) {
if (++*count >= limit) {
task_runner->PostTask(FROM_HERE, closure);
}
}
class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
public:
MOCK_METHOD4(OnData,
void(const AudioBus* src,
base::TimeTicks capture_time,
double volume,
const AudioGlitchInfo& glitch_info));
MOCK_METHOD0(OnError, void());
};
class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback {
public:
static const int kMaxBufferSize = 2 * 2 * 480 * 100 * 10;
explicit WriteToFileAudioSink(const char* file_name)
: buffer_(0, kMaxBufferSize), file_(fopen(file_name, "wb")) {}
~WriteToFileAudioSink() override {
size_t bytes_written = 0;
while (bytes_written < bytes_to_write_) {
const base::span<const uint8_t> chunk = buffer_.GetCurrentChunk();
if (chunk.empty()) {
break;
}
UNSAFE_TODO(fwrite(chunk.data(), 1, chunk.size(), file_));
buffer_.Seek(chunk.size());
bytes_written += chunk.size();
}
fclose(file_);
}
void OnData(const AudioBus* src,
base::TimeTicks capture_time,
double volume,
const AudioGlitchInfo& glitch_info) override {
const int num_samples = src->frames() * src->channels();
auto interleaved = base::HeapArray<int16_t>::Uninit(num_samples);
src->ToInterleaved<SignedInt16SampleTypeTraits>(src->frames(),
interleaved.data());
if (buffer_.Append(base::as_bytes(interleaved.as_span()))) {
bytes_to_write_ += interleaved.as_span().size_bytes();
}
}
void OnError() override {}
private:
media::SeekableBuffer buffer_;
raw_ptr<FILE> file_;
size_t bytes_to_write_ = 0;
};
class MacAudioInputTest : public testing::Test {
protected:
MacAudioInputTest()
: task_environment_(
base::test::SingleThreadTaskEnvironment::MainThreadType::UI),
audio_manager_(AudioManager::CreateForTesting(
std::make_unique<TestAudioThread>())) {
base::RunLoop().RunUntilIdle();
}
~MacAudioInputTest() override { audio_manager_->Shutdown(); }
bool InputDevicesAvailable() {
#if BUILDFLAG(IS_APPLE) && defined(ARCH_CPU_ARM64)
return false;
#else
return AudioDeviceInfoAccessorForTests(audio_manager_.get())
.HasAudioInputDevices();
#endif
}
int HardwareSampleRateForDefaultInputDevice() {
AudioDeviceID input_device_id = kAudioObjectUnknown;
#if BUILDFLAG(IS_MAC)
AudioManagerMac::GetDefaultInputDevice(&input_device_id);
#endif
auto* manager = static_cast<AudioManagerApple*>(audio_manager_.get());
return manager->HardwareSampleRateForDevice(input_device_id);
}
AudioInputStream* CreateDefaultAudioInputStream() {
const int fs = HardwareSampleRateForDefaultInputDevice();
const int samples_per_packet = fs / 100;
#if BUILDFLAG(IS_MAC)
ChannelLayoutConfig channel_layout_config = ChannelLayoutConfig::Stereo();
#else
ChannelLayoutConfig channel_layout_config = ChannelLayoutConfig::Mono();
#endif
AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
channel_layout_config, fs, samples_per_packet),
AudioDeviceDescription::kDefaultDeviceId,
base::BindRepeating(&MacAudioInputTest::OnLogMessage,
base::Unretained(this)));
EXPECT_TRUE(ais);
return ais;
}
AudioInputStream* CreateAudioInputStream(
ChannelLayoutConfig channel_layout_config) {
const int fs = HardwareSampleRateForDefaultInputDevice();
const int samples_per_packet = fs / 100;
AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
channel_layout_config, fs, samples_per_packet),
AudioDeviceDescription::kDefaultDeviceId,
base::BindRepeating(&MacAudioInputTest::OnLogMessage,
base::Unretained(this)));
EXPECT_TRUE(ais);
return ais;
}
void OnLogMessage(const std::string& message) { log_message_ = message; }
base::test::SingleThreadTaskEnvironment task_environment_;
std::unique_ptr<AudioManager> audio_manager_;
std::string log_message_;
};
TEST_F(MacAudioInputTest, AUAudioInputStreamCreateAndClose) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
AudioInputStream* ais = CreateDefaultAudioInputStream();
ais->Close();
}
TEST_F(MacAudioInputTest, AUAudioInputStreamOpenAndClose) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
AudioInputStream* ais = CreateDefaultAudioInputStream();
EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
ais->Close();
}
TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartAndClose) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
AudioInputStream* ais = CreateDefaultAudioInputStream();
EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
MockAudioInputCallback sink;
ais->Start(&sink);
ais->Close();
}
TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartStopAndClose) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
AudioInputStream* ais = CreateDefaultAudioInputStream();
EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
MockAudioInputCallback sink;
ais->Start(&sink);
ais->Stop();
ais->Close();
}
TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyMonoRecording) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
int count = 0;
AudioInputStream* ais = CreateAudioInputStream(ChannelLayoutConfig::Mono());
EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
MockAudioInputCallback sink;
base::RunLoop run_loop;
EXPECT_CALL(sink, OnData(NotNull(), _, _, _))
.Times(AtLeast(10))
.WillRepeatedly(CheckCountAndPostQuitTask(
&count, 10, task_environment_.GetMainThreadTaskRunner(),
run_loop.QuitClosure()));
ais->Start(&sink);
run_loop.Run();
ais->Stop();
ais->Close();
EXPECT_FALSE(log_message_.empty());
}
TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyStereoRecording) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
int count = 0;
AudioInputStream* ais = CreateAudioInputStream(ChannelLayoutConfig::Stereo());
EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
MockAudioInputCallback sink;
base::RunLoop run_loop;
EXPECT_CALL(sink, OnData(NotNull(), _, _, _))
.Times(AtLeast(10))
.WillRepeatedly(CheckCountAndPostQuitTask(
&count, 10, task_environment_.GetMainThreadTaskRunner(),
run_loop.QuitClosure()));
ais->Start(&sink);
run_loop.Run();
ais->Stop();
ais->Close();
EXPECT_FALSE(log_message_.empty());
}
TEST_F(MacAudioInputTest, DISABLED_AUAudioInputStreamRecordToFile) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
static constexpr char kFileName[] = "out_stereo_10sec.pcm";
const int fs = HardwareSampleRateForDefaultInputDevice();
AudioInputStream* ais = CreateDefaultAudioInputStream();
EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
UNSAFE_TODO(fprintf(stderr, " File name : %s\n", kFileName));
fprintf(stderr, " Sample rate: %d\n", fs);
WriteToFileAudioSink file_sink(kFileName);
fprintf(stderr, " >> Speak into the mic while recording...\n");
ais->Start(&file_sink);
base::PlatformThread::Sleep(TestTimeouts::action_timeout());
ais->Stop();
fprintf(stderr, " >> Recording has stopped.\n");
ais->Close();
}
TEST(MacAudioInputUpmixerTest, Upmix16bit) {
constexpr int kNumFrames = 512;
constexpr int kBytesPerSample = sizeof(int16_t);
std::array<int16_t, kNumFrames> mono;
std::array<int16_t, kNumFrames * 2> stereo;
for (int i = 0; i != kNumFrames; ++i) {
mono[i] = i;
stereo[i] = i;
}
AudioBuffer audio_buffer;
audio_buffer.mNumberChannels = 2;
audio_buffer.mDataByteSize = kNumFrames * kBytesPerSample * 2;
audio_buffer.mData = stereo.data();
AUAudioInputStream::UpmixMonoToStereoInPlace(&audio_buffer, kBytesPerSample);
for (int i = 0; i != kNumFrames; ++i) {
ASSERT_EQ(mono[i], stereo[i * 2]);
ASSERT_EQ(mono[i], stereo[i * 2 + 1]);
}
}
TEST(MacAudioInputUpmixerTest, Upmix32bit) {
constexpr int kNumFrames = 512;
constexpr int kBytesPerSample = sizeof(int32_t);
std::array<int32_t, kNumFrames> mono;
std::array<int32_t, kNumFrames * 2> stereo;
for (int i = 0; i != kNumFrames; ++i) {
mono[i] = i;
stereo[i] = i;
}
AudioBuffer audio_buffer;
audio_buffer.mNumberChannels = 2;
audio_buffer.mDataByteSize = kNumFrames * kBytesPerSample * 2;
audio_buffer.mData = stereo.data();
AUAudioInputStream::UpmixMonoToStereoInPlace(&audio_buffer, kBytesPerSample);
for (int i = 0; i != kNumFrames; ++i) {
ASSERT_EQ(mono[i], stereo[i * 2]);
ASSERT_EQ(mono[i], stereo[i * 2 + 1]);
}
}
}