blob: 2eaadc6fc4f7abb9388808f3fefd191d4762fde1 [file] [log] [blame]
/*
* Copyright 2021 HIMSA II K/S - www.himsa.com.
* Represented by EHIMA - www.ehima.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "client_audio.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <chrono>
#include <future>
#include "audio_hal_interface/le_audio_software.h"
#include "base/bind_helpers.h"
#include "common/message_loop_thread.h"
#include "hardware/bluetooth.h"
#include "osi/include/wakelock.h"
using ::testing::_;
using ::testing::Assign;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::Return;
using ::testing::ReturnPointee;
using ::testing::SaveArg;
using std::chrono_literals::operator""ms;
bluetooth::common::MessageLoopThread message_loop_thread("test message loop");
bluetooth::common::MessageLoopThread* get_main_thread() {
return &message_loop_thread;
}
bt_status_t do_in_main_thread(const base::Location& from_here,
base::OnceClosure task) {
if (!message_loop_thread.DoInThread(from_here, std::move(task))) {
LOG(ERROR) << __func__ << ": failed from " << from_here.ToString();
return BT_STATUS_FAIL;
}
return BT_STATUS_SUCCESS;
}
static base::MessageLoop* message_loop_;
base::MessageLoop* get_main_message_loop() { return message_loop_; }
static void init_message_loop_thread() {
message_loop_thread.StartUp();
if (!message_loop_thread.IsRunning()) {
FAIL() << "unable to create message loop thread.";
}
if (!message_loop_thread.EnableRealTimeScheduling())
LOG(ERROR) << "Unable to set real time scheduling";
message_loop_ = message_loop_thread.message_loop();
if (message_loop_ == nullptr) FAIL() << "unable to get message loop.";
}
static void cleanup_message_loop_thread() {
message_loop_ = nullptr;
message_loop_thread.ShutDown();
}
using bluetooth::audio::le_audio::LeAudioClientInterface;
class MockLeAudioClientInterfaceSink : public LeAudioClientInterface::Sink {
public:
MOCK_METHOD((void), Cleanup, (), (override));
MOCK_METHOD((void), SetPcmParameters,
(const LeAudioClientInterface::PcmParameters& params),
(override));
MOCK_METHOD((void), SetRemoteDelay, (uint16_t delay_report_ms), (override));
MOCK_METHOD((void), StartSession, (), (override));
MOCK_METHOD((void), StopSession, (), (override));
MOCK_METHOD((void), ConfirmStreamingRequest, (), (override));
MOCK_METHOD((void), CancelStreamingRequest, (), (override));
MOCK_METHOD((size_t), Read, (uint8_t * p_buf, uint32_t len));
};
class MockLeAudioClientInterfaceSource : public LeAudioClientInterface::Source {
public:
MOCK_METHOD((void), Cleanup, (), (override));
MOCK_METHOD((void), SetPcmParameters,
(const LeAudioClientInterface::PcmParameters& params),
(override));
MOCK_METHOD((void), SetRemoteDelay, (uint16_t delay_report_ms), (override));
MOCK_METHOD((void), StartSession, (), (override));
MOCK_METHOD((void), StopSession, (), (override));
MOCK_METHOD((void), ConfirmStreamingRequest, (), (override));
MOCK_METHOD((void), CancelStreamingRequest, (), (override));
MOCK_METHOD((size_t), Write, (const uint8_t* p_buf, uint32_t len));
};
class MockLeAudioClientInterface : public LeAudioClientInterface {
public:
MockLeAudioClientInterface() = default;
~MockLeAudioClientInterface() = default;
MOCK_METHOD((Sink*), GetSink,
(bluetooth::audio::le_audio::StreamCallbacks stream_cb,
bluetooth::common::MessageLoopThread* message_loop));
MOCK_METHOD((Source*), GetSource,
(bluetooth::audio::le_audio::StreamCallbacks stream_cb,
bluetooth::common::MessageLoopThread* message_loop));
};
LeAudioClientInterface* mockInterface;
namespace bluetooth {
namespace audio {
namespace le_audio {
MockLeAudioClientInterface* interface_mock;
MockLeAudioClientInterfaceSink* sink_mock;
MockLeAudioClientInterfaceSource* source_mock;
LeAudioClientInterface* LeAudioClientInterface::Get() { return interface_mock; }
LeAudioClientInterface::Sink* LeAudioClientInterface::GetSink(
StreamCallbacks stream_cb,
bluetooth::common::MessageLoopThread* message_loop) {
return interface_mock->GetSink(stream_cb, message_loop);
}
LeAudioClientInterface::Source* LeAudioClientInterface::GetSource(
StreamCallbacks stream_cb,
bluetooth::common::MessageLoopThread* message_loop) {
return interface_mock->GetSource(stream_cb, message_loop);
}
bool LeAudioClientInterface::ReleaseSink(LeAudioClientInterface::Sink* sink) {
return true;
}
bool LeAudioClientInterface::ReleaseSource(
LeAudioClientInterface::Source* source) {
return true;
}
void LeAudioClientInterface::Sink::Cleanup() {}
void LeAudioClientInterface::Sink::SetPcmParameters(
const PcmParameters& params) {}
void LeAudioClientInterface::Sink::SetRemoteDelay(uint16_t delay_report_ms) {}
void LeAudioClientInterface::Sink::StartSession() {}
void LeAudioClientInterface::Sink::StopSession() {}
void LeAudioClientInterface::Sink::ConfirmStreamingRequest(){};
void LeAudioClientInterface::Sink::CancelStreamingRequest(){};
void LeAudioClientInterface::Source::Cleanup() {}
void LeAudioClientInterface::Source::SetPcmParameters(
const PcmParameters& params) {}
void LeAudioClientInterface::Source::SetRemoteDelay(uint16_t delay_report_ms) {}
void LeAudioClientInterface::Source::StartSession() {}
void LeAudioClientInterface::Source::StopSession() {}
void LeAudioClientInterface::Source::ConfirmStreamingRequest(){};
void LeAudioClientInterface::Source::CancelStreamingRequest(){};
size_t LeAudioClientInterface::Source::Write(const uint8_t* p_buf,
uint32_t len) {
return source_mock->Write(p_buf, len);
}
size_t LeAudioClientInterface::Sink::Read(uint8_t* p_buf, uint32_t len) {
return sink_mock->Read(p_buf, len);
}
} // namespace le_audio
} // namespace audio
} // namespace bluetooth
class MockLeAudioClientAudioSinkEventReceiver
: public LeAudioClientAudioSinkReceiver {
public:
MOCK_METHOD((void), OnAudioDataReady, (const std::vector<uint8_t>& data),
(override));
MOCK_METHOD((void), OnAudioSuspend, (std::promise<void> do_suspend_promise),
(override));
MOCK_METHOD((void), OnAudioResume, (std::promise<void> do_resume_promise),
(override));
MOCK_METHOD((void), OnAudioMetadataUpdate,
(std::promise<void> do_update_metadata_promise,
const source_metadata_t& source_metadata),
(override));
};
class MockLeAudioClientAudioSourceEventReceiver
: public LeAudioClientAudioSourceReceiver {
public:
MOCK_METHOD((void), OnAudioSuspend, (std::promise<void> do_suspend_promise),
(override));
MOCK_METHOD((void), OnAudioResume, (std::promise<void> do_resume_promise),
(override));
};
class LeAudioClientAudioTest : public ::testing::Test {
public:
void SetUp(void) override {
init_message_loop_thread();
bluetooth::audio::le_audio::interface_mock = &mock_client_interface_;
bluetooth::audio::le_audio::sink_mock = &mock_client_interface_sink_;
bluetooth::audio::le_audio::source_mock = &mock_client_interface_source_;
// Init sink Audio HAL mock
is_sink_acquired = false;
hal_sink_stream_cb = {.on_suspend_ = nullptr, .on_resume_ = nullptr};
ON_CALL(mock_client_interface_, GetSink(_, _))
.WillByDefault(DoAll(SaveArg<0>(&hal_sink_stream_cb),
Assign(&is_sink_acquired, true),
Return(bluetooth::audio::le_audio::sink_mock)));
ON_CALL(mock_client_interface_sink_, Cleanup())
.WillByDefault(Assign(&is_sink_acquired, false));
// Init source Audio HAL mock
is_source_acquired = false;
hal_source_stream_cb = {.on_suspend_ = nullptr, .on_resume_ = nullptr};
ON_CALL(mock_client_interface_, GetSource(_, _))
.WillByDefault(DoAll(SaveArg<0>(&hal_source_stream_cb),
Assign(&is_source_acquired, true),
Return(bluetooth::audio::le_audio::source_mock)));
ON_CALL(mock_client_interface_source_, Cleanup())
.WillByDefault(Assign(&is_source_acquired, false));
}
void AcquireAudioSink(void) {
audio_sink_instance_ = LeAudioClientAudioSink::Acquire();
}
void ReleaseAudioSink(void) {
LeAudioClientAudioSink::Release(audio_sink_instance_);
audio_sink_instance_ = nullptr;
}
void AcquireAudioSource(void) {
audio_source_instance_ = LeAudioClientAudioSource::Acquire();
}
void ReleaseAudioSource(void) {
LeAudioClientAudioSource::Release(audio_source_instance_);
audio_source_instance_ = nullptr;
}
void TearDown(void) override {
/* We have to call Cleanup to tidy up some static variables.
* If on the HAL end Source is running it means we are running the Sink
* on our end, and vice versa.
*/
if (is_source_acquired == true) ReleaseAudioSink();
if (is_sink_acquired == true) ReleaseAudioSource();
cleanup_message_loop_thread();
bluetooth::audio::le_audio::sink_mock = nullptr;
bluetooth::audio::le_audio::source_mock = nullptr;
}
MockLeAudioClientInterface mock_client_interface_;
MockLeAudioClientInterfaceSink mock_client_interface_sink_;
MockLeAudioClientInterfaceSource mock_client_interface_source_;
MockLeAudioClientAudioSinkEventReceiver mock_hal_sink_event_receiver_;
MockLeAudioClientAudioSourceEventReceiver mock_hal_source_event_receiver_;
bool is_source_acquired = false;
bool is_sink_acquired = false;
const void* audio_sink_instance_ = nullptr;
const void* audio_source_instance_ = nullptr;
bluetooth::audio::le_audio::StreamCallbacks hal_source_stream_cb;
bluetooth::audio::le_audio::StreamCallbacks hal_sink_stream_cb;
const LeAudioCodecConfiguration default_codec_conf{
.num_channels = LeAudioCodecConfiguration::kChannelNumberMono,
.sample_rate = LeAudioCodecConfiguration::kSampleRate44100,
.bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample24,
.data_interval_us = LeAudioCodecConfiguration::kInterval10000Us,
};
};
TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkInitializeCleanup) {
EXPECT_CALL(mock_client_interface_, GetSource(_, _))
.WillOnce(DoAll(Assign(&is_source_acquired, true),
Return(bluetooth::audio::le_audio::source_mock)));
AcquireAudioSink();
EXPECT_CALL(mock_client_interface_source_, Cleanup())
.WillOnce(Assign(&is_source_acquired, false));
ReleaseAudioSink();
}
TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceInitializeCleanup) {
EXPECT_CALL(mock_client_interface_, GetSink(_, _))
.WillOnce(DoAll(Assign(&is_sink_acquired, true),
Return(bluetooth::audio::le_audio::sink_mock)));
AcquireAudioSource();
EXPECT_CALL(mock_client_interface_sink_, Cleanup())
.WillOnce(Assign(&is_sink_acquired, false));
ReleaseAudioSource();
}
TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkStartStop) {
LeAudioClientInterface::PcmParameters params;
EXPECT_CALL(mock_client_interface_source_, SetPcmParameters(_))
.Times(1)
.WillOnce(SaveArg<0>(&params));
EXPECT_CALL(mock_client_interface_source_, StartSession()).Times(1);
AcquireAudioSink();
ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf,
&mock_hal_source_event_receiver_));
ASSERT_EQ(params.channels_count,
bluetooth::audio::le_audio::kChannelNumberMono);
ASSERT_EQ(params.sample_rate, bluetooth::audio::le_audio::kSampleRate44100);
ASSERT_EQ(params.bits_per_sample,
bluetooth::audio::le_audio::kBitsPerSample24);
ASSERT_EQ(params.data_interval_us, 10000u);
EXPECT_CALL(mock_client_interface_source_, StopSession()).Times(1);
LeAudioClientAudioSink::Stop();
}
TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceStartStop) {
LeAudioClientInterface::PcmParameters params;
EXPECT_CALL(mock_client_interface_sink_, SetPcmParameters(_))
.Times(1)
.WillOnce(SaveArg<0>(&params));
EXPECT_CALL(mock_client_interface_sink_, StartSession()).Times(1);
AcquireAudioSource();
ASSERT_TRUE(LeAudioClientAudioSource::Start(default_codec_conf,
&mock_hal_sink_event_receiver_));
ASSERT_EQ(params.channels_count,
bluetooth::audio::le_audio::kChannelNumberMono);
ASSERT_EQ(params.sample_rate, bluetooth::audio::le_audio::kSampleRate44100);
ASSERT_EQ(params.bits_per_sample,
bluetooth::audio::le_audio::kBitsPerSample24);
ASSERT_EQ(params.data_interval_us, 10000u);
EXPECT_CALL(mock_client_interface_sink_, StopSession()).Times(1);
LeAudioClientAudioSource::Stop();
}
TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkSendData) {
AcquireAudioSink();
ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf,
&mock_hal_source_event_receiver_));
const uint8_t* exp_p = nullptr;
uint32_t exp_len = 0;
uint8_t input_buf[] = {
0x02,
0x03,
0x05,
0x19,
};
ON_CALL(mock_client_interface_source_, Write(_, _))
.WillByDefault(DoAll(SaveArg<0>(&exp_p), SaveArg<1>(&exp_len),
ReturnPointee(&exp_len)));
ASSERT_EQ(LeAudioClientAudioSink::SendData(input_buf, sizeof(input_buf)),
sizeof(input_buf));
ASSERT_EQ(exp_len, sizeof(input_buf));
ASSERT_EQ(exp_p, input_buf);
LeAudioClientAudioSource::Stop();
}
TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkSuspend) {
AcquireAudioSink();
ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf,
&mock_hal_source_event_receiver_));
ASSERT_NE(hal_source_stream_cb.on_suspend_, nullptr);
/* Expect LeAudio registered event listener to get called when HAL calls the
* client_audio's internal suspend callback.
*/
EXPECT_CALL(mock_hal_source_event_receiver_, OnAudioSuspend(_)).Times(1);
ASSERT_TRUE(hal_source_stream_cb.on_suspend_());
}
TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceSuspend) {
AcquireAudioSource();
ASSERT_TRUE(LeAudioClientAudioSource::Start(default_codec_conf,
&mock_hal_sink_event_receiver_));
ASSERT_NE(hal_sink_stream_cb.on_suspend_, nullptr);
/* Expect LeAudio registered event listener to get called when HAL calls the
* client_audio's internal suspend callback.
*/
EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioSuspend(_)).Times(1);
ASSERT_TRUE(hal_sink_stream_cb.on_suspend_());
}
TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkResume) {
AcquireAudioSink();
ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf,
&mock_hal_source_event_receiver_));
ASSERT_NE(hal_source_stream_cb.on_resume_, nullptr);
/* Expect LeAudio registered event listener to get called when HAL calls the
* client_audio's internal resume callback.
*/
EXPECT_CALL(mock_hal_source_event_receiver_, OnAudioResume(_)).Times(1);
bool start_media_task = false;
ASSERT_TRUE(hal_source_stream_cb.on_resume_(start_media_task));
}
TEST_F(LeAudioClientAudioTest,
testLeAudioClientAudioSourceResumeStartSourceTask) {
const LeAudioCodecConfiguration codec_conf{
.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo,
.sample_rate = LeAudioCodecConfiguration::kSampleRate16000,
.bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample24,
.data_interval_us = LeAudioCodecConfiguration::kInterval10000Us,
};
AcquireAudioSource();
ASSERT_TRUE(LeAudioClientAudioSource::Start(codec_conf,
&mock_hal_sink_event_receiver_));
std::chrono::time_point<std::chrono::system_clock> resumed_ts;
std::chrono::time_point<std::chrono::system_clock> executed_ts;
std::promise<void> promise;
auto future = promise.get_future();
uint32_t calculated_bytes_per_tick = 0;
EXPECT_CALL(mock_client_interface_sink_, Read(_, _))
.Times(AtLeast(1))
.WillOnce(Invoke([&](uint8_t* p_buf, uint32_t len) -> uint32_t {
executed_ts = std::chrono::system_clock::now();
calculated_bytes_per_tick = len;
// fake some data from audio framework
for (uint32_t i = 0u; i < len; ++i) {
p_buf[i] = i;
}
// Return exactly as much data as requested
promise.set_value();
return len;
}));
std::promise<void> data_promise;
auto data_future = data_promise.get_future();
/* Expect this callback to be called to Client by the HAL glue layer */
std::vector<uint8_t> media_data_to_send;
EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioDataReady(_))
.WillOnce(Invoke([&](const std::vector<uint8_t>& data) -> void {
media_data_to_send = std::move(data);
data_promise.set_value();
}));
/* Expect LeAudio registered event listener to get called when HAL calls the
* client_audio's internal resume callback.
*/
ASSERT_NE(hal_sink_stream_cb.on_resume_, nullptr);
EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioResume(_)).Times(1);
resumed_ts = std::chrono::system_clock::now();
bool start_media_task = true;
ASSERT_TRUE(hal_sink_stream_cb.on_resume_(start_media_task));
LeAudioClientAudioSource::ConfirmStreamingRequest();
ASSERT_EQ(future.wait_for(std::chrono::seconds(1)),
std::future_status::ready);
ASSERT_EQ(data_future.wait_for(std::chrono::seconds(1)),
std::future_status::ready);
// Check agains expected payload size
const uint32_t channel_bytes_per_sample = (24 /*bps*/ / 8);
const uint32_t channel_bytes_per_10ms_at_16000Hz =
((10ms).count() * channel_bytes_per_sample * 16000 /*Hz*/) /
(1000ms).count();
// Expect 2 channel (stereo) data
ASSERT_EQ(calculated_bytes_per_tick, 2 * channel_bytes_per_10ms_at_16000Hz);
// Verify callback call interval for the requested 10ms (+2ms error margin)
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(
executed_ts - resumed_ts);
EXPECT_TRUE((delta >= 10ms) && (delta <= 12ms));
// Verify if we got just right amount of data in the callback call
ASSERT_EQ(media_data_to_send.size(), calculated_bytes_per_tick);
}
TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceResume) {
AcquireAudioSource();
ASSERT_TRUE(LeAudioClientAudioSource::Start(default_codec_conf,
&mock_hal_sink_event_receiver_));
ASSERT_NE(hal_sink_stream_cb.on_resume_, nullptr);
/* Expect LeAudio registered event listener to get called when HAL calls the
* client_audio's internal resume callback.
*/
EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioResume(_)).Times(1);
bool start_media_task = false;
ASSERT_TRUE(hal_sink_stream_cb.on_resume_(start_media_task));
}