Add linux audio PAL
Bug: 144189870
Test: `atest --host chre_unit_tests`,
`run_pal_impl_tests.sh`
Change-Id: I350a4922fa01157a87738286dd9c2fb9d7bdb811
diff --git a/Android.bp b/Android.bp
index 793fc39..f9882ea 100644
--- a/Android.bp
+++ b/Android.bp
@@ -506,6 +506,7 @@
"platform/linux/host_link.cc",
"platform/linux/memory_manager.cc",
"platform/linux/memory.cc",
+ "platform/linux/pal_audio.cc",
"platform/linux/pal_ble.cc",
"platform/linux/pal_gnss.cc",
"platform/linux/pal_nan.cc",
diff --git a/pal/pal.mk b/pal/pal.mk
index 5bc7c9c..16c546a 100644
--- a/pal/pal.mk
+++ b/pal/pal.mk
@@ -10,8 +10,11 @@
# GoogleTest Source Files ######################################################
GOOGLETEST_CFLAGS += -I$(CHRE_PREFIX)/pal/tests/include
+
GOOGLETEST_SRCS += $(CHRE_PREFIX)/pal/tests/src/version_test.cc
GOOGLETEST_SRCS += $(CHRE_PREFIX)/pal/tests/src/wwan_test.cc
+
+GOOGLETEST_PAL_IMPL_SRCS += $(CHRE_PREFIX)/pal/tests/src/audio_pal_impl_test.cc
GOOGLETEST_PAL_IMPL_SRCS += $(CHRE_PREFIX)/pal/tests/src/gnss_pal_impl_test.cc
+GOOGLETEST_PAL_IMPL_SRCS += $(CHRE_PREFIX)/pal/tests/src/sensor_pal_impl_test.cc
GOOGLETEST_PAL_IMPL_SRCS += $(CHRE_PREFIX)/pal/tests/src/wifi_pal_impl_test.cc
-GOOGLETEST_PAL_IMPL_SRCS += $(CHRE_PREFIX)/pal/tests/src/sensor_pal_impl_test.cc
\ No newline at end of file
diff --git a/pal/tests/src/audio_pal_impl_test.cc b/pal/tests/src/audio_pal_impl_test.cc
new file mode 100644
index 0000000..2e7c67a
--- /dev/null
+++ b/pal/tests/src/audio_pal_impl_test.cc
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 <cstdint>
+
+#include "chre/pal/audio.h"
+#include "chre/platform/condition_variable.h"
+#include "chre/platform/mutex.h"
+#include "chre/platform/shared/pal_system_api.h"
+#include "chre/util/lock_guard.h"
+#include "chre/util/macros.h"
+#include "chre/util/optional.h"
+#include "chre/util/time.h"
+#include "chre/util/unique_ptr.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using ::chre::ConditionVariable;
+using ::chre::gChrePalSystemApi;
+using ::chre::kOneMillisecondInNanoseconds;
+using ::chre::LockGuard;
+using ::chre::MakeUnique;
+using ::chre::Mutex;
+using ::chre::Nanoseconds;
+using ::chre::Optional;
+using ::chre::UniquePtr;
+
+class Callbacks {
+ public:
+ void audioDataEventCallback(struct chreAudioDataEvent *event) {
+ LockGuard<Mutex> lock(mMutex);
+ if (!mDataEvent.has_value()) {
+ mDataEvent = event;
+ mCondVarDataEvents.notify_one();
+ }
+ }
+
+ void audioAvailabilityCallback(uint32_t handle, bool available) {
+ UNUSED_VAR(handle);
+ UNUSED_VAR(available);
+ }
+
+ Optional<struct chreAudioDataEvent *> mDataEvent;
+
+ //! Synchronize access to class members.
+ Mutex mMutex;
+ ConditionVariable mCondVarDataEvents;
+};
+
+UniquePtr<Callbacks> gCallbacks = nullptr;
+
+void audioDataEventCallback(struct chreAudioDataEvent *event) {
+ if (gCallbacks != nullptr) {
+ gCallbacks->audioDataEventCallback(event);
+ }
+}
+
+void audioAvailabilityCallback(uint32_t handle, bool available) {
+ if (gCallbacks != nullptr) {
+ gCallbacks->audioAvailabilityCallback(handle, available);
+ }
+}
+
+class PalAudioTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ gCallbacks = MakeUnique<Callbacks>();
+ mApi = chrePalAudioGetApi(CHRE_PAL_AUDIO_API_CURRENT_VERSION);
+ ASSERT_NE(mApi, nullptr);
+ EXPECT_EQ(mApi->moduleVersion, CHRE_PAL_AUDIO_API_CURRENT_VERSION);
+ ASSERT_TRUE(mApi->open(&gChrePalSystemApi, &mPalCallbacks));
+ }
+
+ void TearDown() override {
+ gCallbacks = nullptr;
+ if (mApi != nullptr) {
+ mApi->close();
+ }
+ }
+
+ //! CHRE PAL implementation API.
+ const struct chrePalAudioApi *mApi;
+
+ const struct chrePalAudioCallbacks mPalCallbacks = {
+ .audioDataEventCallback = audioDataEventCallback,
+ .audioAvailabilityCallback = audioAvailabilityCallback,
+ };
+};
+
+TEST_F(PalAudioTest, GetAudioSourceInfoForExistingSource) {
+ struct chreAudioSource audioSource;
+
+ EXPECT_EQ(mApi->getSourceCount(), 1);
+ EXPECT_TRUE(mApi->getAudioSource(0, &audioSource));
+ EXPECT_STREQ(audioSource.name, "Test Source");
+}
+
+TEST_F(PalAudioTest, GetAudioSourceInfoForNonExistingSource) {
+ struct chreAudioSource audioSource;
+
+ EXPECT_EQ(mApi->getSourceCount(), 1);
+ EXPECT_FALSE(mApi->getAudioSource(10, &audioSource));
+}
+
+TEST_F(PalAudioTest, GetDataEvent) {
+ EXPECT_TRUE(mApi->requestAudioDataEvent(0 /*handle*/, 1000 /*numSamples*/,
+ 100 /*eventDelaysNs*/));
+
+ LockGuard<Mutex> lock(gCallbacks->mMutex);
+ gCallbacks->mCondVarDataEvents.wait_for(
+ gCallbacks->mMutex, Nanoseconds(kOneMillisecondInNanoseconds));
+ EXPECT_TRUE(gCallbacks->mDataEvent.has_value());
+ struct chreAudioDataEvent *event = gCallbacks->mDataEvent.value();
+ EXPECT_EQ(event->handle, 0);
+ EXPECT_EQ(event->sampleCount, 1000);
+
+ mApi->releaseAudioDataEvent(event);
+}
+
+} // namespace
\ No newline at end of file
diff --git a/platform/linux/pal_audio.cc b/platform/linux/pal_audio.cc
new file mode 100644
index 0000000..b143522
--- /dev/null
+++ b/platform/linux/pal_audio.cc
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 "chre/pal/audio.h"
+
+#include "chre/platform/memory.h"
+#include "chre/util/macros.h"
+#include "chre/util/memory.h"
+#include "chre/util/unique_ptr.h"
+
+#include <chrono>
+#include <cinttypes>
+#include <cstdint>
+#include <future>
+#include <thread>
+
+/**
+ * A simulated implementation of the audio PAL for the linux platform.
+ */
+namespace {
+const struct chrePalSystemApi *gSystemApi = nullptr;
+const struct chrePalAudioCallbacks *gCallbacks = nullptr;
+
+//! Thread to deliver asynchronous audio data after a CHRE request.
+std::thread gHandle0Thread;
+std::promise<void> gStopHandle0Thread;
+constexpr uint32_t kHandle0SampleRate = 16000;
+
+void stopHandle0Thread() {
+ if (gHandle0Thread.joinable()) {
+ gStopHandle0Thread.set_value();
+ gHandle0Thread.join();
+ }
+}
+
+void chrePalAudioApiClose(void) {
+ stopHandle0Thread();
+}
+
+bool chrePalAudioApiOpen(const struct chrePalSystemApi *systemApi,
+ const struct chrePalAudioCallbacks *callbacks) {
+ chrePalAudioApiClose();
+
+ if (systemApi != nullptr && callbacks != nullptr) {
+ gSystemApi = systemApi;
+ gCallbacks = callbacks;
+ return true;
+ }
+
+ return false;
+}
+
+void sendHandle0Events(uint64_t delayNs, uint32_t numSamples) {
+ std::future<void> signal = gStopHandle0Thread.get_future();
+ if (signal.wait_for(std::chrono::nanoseconds(delayNs)) ==
+ std::future_status::timeout) {
+ auto data = chre::MakeUniqueZeroFill<struct chreAudioDataEvent>();
+
+ data->version = CHRE_AUDIO_DATA_EVENT_VERSION;
+ data->handle = 0;
+ data->timestamp = gSystemApi->getCurrentTime();
+ data->sampleRate = kHandle0SampleRate;
+ data->sampleCount = numSamples;
+ data->format = CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW;
+ data->samplesULaw8 =
+ static_cast<const uint8_t *>(chre::memoryAlloc(numSamples));
+
+ gCallbacks->audioDataEventCallback(data.release());
+ }
+}
+
+bool chrePalAudioApiRequestAudioDataEvent(uint32_t handle, uint32_t numSamples,
+ uint64_t eventDelayNs) {
+ if (handle != 0) {
+ return false;
+ }
+
+ stopHandle0Thread();
+ if (numSamples > 0) {
+ gStopHandle0Thread = std::promise<void>();
+ gHandle0Thread = std::thread(sendHandle0Events, eventDelayNs, numSamples);
+ }
+
+ return true;
+}
+
+void chrePalAudioApiCancelAudioDataEvent(uint32_t handle) {
+ if (handle == 0) {
+ stopHandle0Thread();
+ }
+}
+
+void chrePalAudioApiReleaseAudioDataEvent(struct chreAudioDataEvent *event) {
+ if (event->format == CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW) {
+ chre::memoryFree((void *)event->samplesULaw8);
+ } else if (event->format == CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM) {
+ chre::memoryFree((void *)event->samplesS16);
+ }
+ chre::memoryFree(event);
+}
+
+uint32_t chrePalAudioApiGetSourceCount(void) {
+ return 1;
+}
+
+bool chrePalAudioApiGetAudioSource(uint32_t handle,
+ struct chreAudioSource *audioSource) {
+ if (handle != 0) {
+ return false;
+ }
+
+ *audioSource = {
+ .name = "Test Source",
+ .sampleRate = kHandle0SampleRate,
+ .minBufferDuration = 1,
+ .maxBufferDuration = 1000,
+ .format = CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW,
+ };
+
+ return true;
+}
+
+} // namespace
+
+const chrePalAudioApi *chrePalAudioGetApi(uint32_t requestedApiVersion) {
+ static const struct chrePalAudioApi kApi = {
+ .moduleVersion = CHRE_PAL_AUDIO_API_CURRENT_VERSION,
+ .open = chrePalAudioApiOpen,
+ .close = chrePalAudioApiClose,
+ .requestAudioDataEvent = chrePalAudioApiRequestAudioDataEvent,
+ .cancelAudioDataEvent = chrePalAudioApiCancelAudioDataEvent,
+ .releaseAudioDataEvent = chrePalAudioApiReleaseAudioDataEvent,
+ .getSourceCount = chrePalAudioApiGetSourceCount,
+ .getAudioSource = chrePalAudioApiGetAudioSource,
+ };
+
+ if (!CHRE_PAL_VERSIONS_ARE_COMPATIBLE(kApi.moduleVersion,
+ requestedApiVersion)) {
+ return nullptr;
+ } else {
+ return &kApi;
+ }
+}
\ No newline at end of file
diff --git a/platform/platform.mk b/platform/platform.mk
index 91ee4c2..3d14788 100644
--- a/platform/platform.mk
+++ b/platform/platform.mk
@@ -236,6 +236,12 @@
SIM_SRCS += platform/shared/platform_gnss.cc
endif
+# Optional sensor support.
+ifeq ($(CHRE_SENSORS_SUPPORT_ENABLED), true)
+SIM_SRCS += platform/linux/pal_sensor.cc
+SIM_SRCS += platform/shared/platform_sensor_manager.cc
+endif
+
# Optional Wi-Fi support.
ifeq ($(CHRE_WIFI_SUPPORT_ENABLED), true)
ifeq ($(CHRE_WIFI_NAN_SUPPORT_ENABLED), true)
@@ -251,12 +257,6 @@
SIM_SRCS += platform/shared/platform_wwan.cc
endif
-# Optional sensor support.
-ifeq ($(CHRE_SENSORS_SUPPORT_ENABLED), true)
-SIM_SRCS += platform/linux/pal_sensor.cc
-SIM_SRCS += platform/shared/platform_sensor_manager.cc
-endif
-
# Linux-specific Compiler Flags ################################################
GOOGLE_X86_LINUX_CFLAGS += -Iplatform/linux/include