Merge "Add new features to tuner service."
diff --git a/Android.bp b/Android.bp
index 60f0ff1..ee609e1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -57,7 +57,7 @@
min_sdk_version: "29",
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth.updatable",
+ "com.android.bluetooth",
"com.android.media",
"com.android.media.swcodec",
],
@@ -86,7 +86,7 @@
min_sdk_version: "29",
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth.updatable",
+ "com.android.bluetooth",
"com.android.media",
"com.android.media.swcodec",
],
diff --git a/drm/mediadrm/plugins/clearkey/hidl/Android.bp b/drm/mediadrm/plugins/clearkey/hidl/Android.bp
index 6c68532..02ac943 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/Android.bp
+++ b/drm/mediadrm/plugins/clearkey/hidl/Android.bp
@@ -93,6 +93,11 @@
srcs: ["protos/DeviceFiles.proto"],
}
+cc_library {
+ name: "libclearkeyhidl",
+ defaults: ["clearkey_service_defaults"],
+}
+
cc_binary {
name: "android.hardware.drm@1.2-service.clearkey",
defaults: ["clearkey_service_defaults"],
@@ -126,3 +131,37 @@
init_rc: ["android.hardware.drm@1.4-service-lazy.clearkey.rc"],
vintf_fragments: ["manifest_android.hardware.drm@1.4-service.clearkey.xml"],
}
+
+cc_fuzz {
+ name: "clearkeyV1.4_fuzzer",
+ vendor: true,
+ srcs: [
+ "fuzzer/clearkeyV1.4_fuzzer.cpp",
+ ],
+ static_libs: [
+ "libclearkeyhidl",
+ "libclearkeycommon",
+ "libclearkeydevicefiles-protos",
+ "libjsmn",
+ "libprotobuf-cpp-lite",
+ "libutils",
+ ],
+ shared_libs: [
+ "android.hidl.allocator@1.0",
+ "android.hardware.drm@1.0",
+ "android.hardware.drm@1.1",
+ "android.hardware.drm@1.2",
+ "android.hardware.drm@1.3",
+ "android.hardware.drm@1.4",
+ "libcrypto",
+ "libhidlbase",
+ "libhidlmemory",
+ "liblog",
+ ],
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
diff --git a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
index 0cd9375..32d7723 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
+++ b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
@@ -187,7 +187,7 @@
return Status_V1_2::ERROR_DRM_CANNOT_HANDLE;
}
- *defaultUrl = "";
+ *defaultUrl = "https://default.url";
*keyRequestType = KeyRequestType_V1_1::UNKNOWN;
*request = std::vector<uint8_t>();
diff --git a/drm/mediadrm/plugins/clearkey/hidl/fuzzer/README.md b/drm/mediadrm/plugins/clearkey/hidl/fuzzer/README.md
new file mode 100644
index 0000000..cb45460
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/hidl/fuzzer/README.md
@@ -0,0 +1,52 @@
+# Fuzzer for android.hardware.drm@1.4-service.clearkey
+
+## Plugin Design Considerations
+The fuzzer plugin for android.hardware.drm@1.4-service.clearkey is designed based on the understanding of the
+source code and tries to achieve the following:
+
+##### Maximize code coverage
+The configuration parameters are not hardcoded, but instead selected based on
+incoming data. This ensures more code paths are reached by the fuzzer.
+
+android.hardware.drm@1.4-service.clearkey supports the following parameters:
+1. Security Level (parameter name: `securityLevel`)
+2. Mime Type (parameter name: `mimeType`)
+3. Key Type (parameter name: `keyType`)
+4. Crypto Mode (parameter name: `cryptoMode`)
+
+| Parameter| Valid Values| Configured Value|
+|------------- |-------------| ----- |
+| `securityLevel` | 0.`SecurityLevel::UNKNOWN` 1.`SecurityLevel::SW_SECURE_CRYPTO` 2.`SecurityLevel::SW_SECURE_DECODE` 3.`SecurityLevel::HW_SECURE_CRYPTO` 4.`SecurityLevel::HW_SECURE_DECODE` 5.`SecurityLevel::HW_SECURE_ALL`| Value obtained from FuzzedDataProvider in the range 0 to 5|
+| `mimeType` | 0.`video/mp4` 1.`video/mpeg` 2.`video/x-flv` 3.`video/mj2` 4.`video/3gp2` 5.`video/3gpp` 6.`video/3gpp2` 7.`audio/mp4` 8.`audio/mpeg` 9.`audio/aac` 10.`audio/3gp2` 11.`audio/3gpp` 12.`audio/3gpp2` 13.`audio/webm` 14.`video/webm` 15.`webm` 16.`cenc` 17.`video/unknown` 18.`audio/unknown`| Value obtained from FuzzedDataProvider in the range 0 to 18|
+| `keyType` | 0.`KeyType::OFFLINE` 1.`KeyType::STREAMING` 2.`KeyType::RELEASE` | Value obtained from FuzzedDataProvider in the range 0 to 2|
+| `cryptoMode` | 0.`Mode::UNENCRYPTED` 1.`Mode::AES_CTR` 2.`Mode::AES_CBC_CTS` 3.`Mode::AES_CBC` | Value obtained from FuzzedDataProvider in the range 0 to 3|
+
+This also ensures that the plugin is always deterministic for any given input.
+
+##### Maximize utilization of input data
+The plugin feeds the entire input data to the module.
+This ensures that the plugin tolerates any kind of input (empty, huge,
+malformed, etc) and doesnt `exit()` on any input and thereby increasing the
+chance of identifying vulnerabilities.
+
+## Build
+
+This describes steps to build clearkeyV1.4_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) clearkeyV1.4_fuzzer
+```
+#### Steps to run
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/${TARGET_ARCH}/clearkeyV1.4_fuzzer/vendor/hw/clearkeyV1.4_fuzzer
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/drm/mediadrm/plugins/clearkey/hidl/fuzzer/clearkeyV1.4_fuzzer.cpp b/drm/mediadrm/plugins/clearkey/hidl/fuzzer/clearkeyV1.4_fuzzer.cpp
new file mode 100644
index 0000000..afe0e6c
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/hidl/fuzzer/clearkeyV1.4_fuzzer.cpp
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2021 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 <include/CreatePluginFactories.h>
+
+#include <android/hidl/allocator/1.0/IAllocator.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <hidlmemory/mapping.h>
+#include <include/ClearKeyDrmProperties.h>
+#include <include/CryptoFactory.h>
+#include <include/CryptoPlugin.h>
+#include <include/DrmPlugin.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+namespace drm = ::android::hardware::drm;
+using namespace std;
+using namespace android;
+using ::android::sp;
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hidl::allocator::V1_0::IAllocator;
+using ::android::hidl::memory::V1_0::IMemory;
+using drm::V1_0::BufferType;
+using drm::V1_0::DestinationBuffer;
+using drm::V1_0::EventType;
+using drm::V1_0::ICryptoPlugin;
+using drm::V1_0::IDrmPlugin;
+using drm::V1_0::IDrmPluginListener;
+using drm::V1_0::KeyedVector;
+using drm::V1_0::KeyStatus;
+using drm::V1_0::KeyStatusType;
+using drm::V1_0::KeyType;
+using drm::V1_0::Mode;
+using drm::V1_0::Pattern;
+using drm::V1_0::SecureStop;
+using drm::V1_0::SharedBuffer;
+using drm::V1_0::Status;
+using drm::V1_0::SubSample;
+using drm::V1_1::DrmMetricGroup;
+using drm::V1_1::HdcpLevel;
+using drm::V1_1::SecureStopRelease;
+using drm::V1_1::SecurityLevel;
+using drm::V1_2::KeySetId;
+using drm::V1_2::OfflineLicenseState;
+using drm::V1_4::clearkey::ICryptoFactory;
+using drm::V1_4::clearkey::IDrmFactory;
+using drm::V1_4::clearkey::kAlgorithmsKey;
+using drm::V1_4::clearkey::kClientIdKey;
+using drm::V1_4::clearkey::kDeviceIdKey;
+using drm::V1_4::clearkey::kDrmErrorTestKey;
+using drm::V1_4::clearkey::kListenerTestSupportKey;
+using drm::V1_4::clearkey::kMetricsKey;
+using drm::V1_4::clearkey::kPluginDescriptionKey;
+using drm::V1_4::clearkey::kVendorKey;
+using drm::V1_4::clearkey::kVersionKey;
+
+typedef ::android::hardware::hidl_vec<uint8_t> SessionId;
+typedef ::android::hardware::hidl_vec<uint8_t> SecureStopId;
+
+static const uint8_t kInvalidUUID[] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60,
+ 0x70, 0x80, 0x10, 0x20, 0x30, 0x40,
+ 0x50, 0x60, 0x70, 0x80};
+
+static const uint8_t kClearKeyUUID[] = {0xE2, 0x71, 0x9D, 0x58, 0xA9, 0x85,
+ 0xB3, 0xC9, 0x78, 0x1A, 0xB0, 0x30,
+ 0xAF, 0x78, 0xD3, 0x0E};
+
+const SecurityLevel kSecurityLevel[] = {
+ SecurityLevel::UNKNOWN, SecurityLevel::SW_SECURE_CRYPTO,
+ SecurityLevel::SW_SECURE_DECODE, SecurityLevel::HW_SECURE_CRYPTO,
+ SecurityLevel::HW_SECURE_DECODE, SecurityLevel::HW_SECURE_ALL};
+
+const char *kMimeType[] = {
+ "video/mp4", "video/mpeg", "video/x-flv", "video/mj2", "video/3gp2",
+ "video/3gpp", "video/3gpp2", "audio/mp4", "audio/mpeg", "audio/aac",
+ "audio/3gp2", "audio/3gpp", "audio/3gpp2", "audio/webm", "video/webm",
+ "webm", "cenc", "video/unknown", "audio/unknown"};
+
+const char *kCipherAlgorithm[] = {"AES/CBC/NoPadding", ""};
+
+const char *kMacAlgorithm[] = {"HmacSHA256", ""};
+
+const char *kRSAAlgorithm[] = {"RSASSA-PSS-SHA1", ""};
+
+const std::string kProperty[] = {kVendorKey,
+ kVersionKey,
+ kPluginDescriptionKey,
+ kAlgorithmsKey,
+ kListenerTestSupportKey,
+ kDrmErrorTestKey,
+ kDeviceIdKey,
+ kClientIdKey,
+ kMetricsKey,
+ "placeholder"};
+
+const KeyType kKeyType[] = {KeyType::OFFLINE, KeyType::STREAMING,
+ KeyType::RELEASE};
+
+const Mode kCryptoMode[] = {Mode::UNENCRYPTED, Mode::AES_CTR, Mode::AES_CBC_CTS,
+ Mode::AES_CBC};
+
+const hidl_vec<uint8_t> validInitData = {
+ // BMFF box header (4 bytes size + 'pssh')
+ 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
+ // full box header (version = 1 flags = 0)
+ 0x01, 0x00, 0x00, 0x00,
+ // system id
+ 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e,
+ 0x52, 0xe2, 0xfb, 0x4b,
+ // number of key ids
+ 0x00, 0x00, 0x00, 0x01,
+ // key id
+ 0x60, 0x06, 0x1e, 0x01, 0x7e, 0x47, 0x7e, 0x87, 0x7e, 0x57, 0xd0, 0x0d,
+ 0x1e, 0xd0, 0x0d, 0x1e,
+ // size of data, must be zero
+ 0x00, 0x00, 0x00, 0x00};
+
+const hidl_vec<uint8_t> validKeyResponse = {
+ 0x7b, 0x22, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22,
+ 0x6b, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x6f, 0x63, 0x74, 0x22, 0x2c,
+ 0x22, 0x6b, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x59, 0x41, 0x59, 0x65,
+ 0x41, 0x58, 0x35, 0x48, 0x66, 0x6f, 0x64, 0x2d, 0x56, 0x39, 0x41,
+ 0x4e, 0x48, 0x74, 0x41, 0x4e, 0x48, 0x67, 0x22, 0x2c, 0x22, 0x6b,
+ 0x22, 0x3a, 0x22, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54, 0x65,
+ 0x73, 0x74, 0x4b, 0x65, 0x79, 0x42, 0x61, 0x73, 0x65, 0x36, 0x34,
+ 0x67, 0x67, 0x67, 0x22, 0x7d, 0x5d, 0x7d, 0x0a};
+
+const size_t kAESBlockSize = 16;
+const size_t kMaxStringLength = 100;
+const size_t kMaxSubSamples = 10;
+const size_t kMaxNumBytes = 1000;
+const size_t kSegmentIndex = 0;
+
+template <typename T, size_t size>
+T getValueFromArray(FuzzedDataProvider *fdp, const T (&arr)[size]) {
+ return arr[fdp->ConsumeIntegralInRange<int32_t>(0, size - 1)];
+}
+
+class TestDrmPluginListener : public IDrmPluginListener {
+public:
+ TestDrmPluginListener() {}
+ virtual ~TestDrmPluginListener() {}
+
+ virtual Return<void> sendEvent(EventType /*eventType*/,
+ const hidl_vec<uint8_t> & /*sessionId*/,
+ const hidl_vec<uint8_t> & /*data*/) override {
+ return Return<void>();
+ }
+
+ virtual Return<void>
+ sendExpirationUpdate(const hidl_vec<uint8_t> & /*sessionId*/,
+ int64_t /*expiryTimeInMS*/) override {
+ return Return<void>();
+ }
+
+ virtual Return<void>
+ sendKeysChange(const hidl_vec<uint8_t> & /*sessionId*/,
+ const hidl_vec<KeyStatus> & /*keyStatusList*/,
+ bool /*hasNewUsableKey*/) override {
+ return Return<void>();
+ }
+};
+
+class ClearKeyFuzzer {
+public:
+ ~ClearKeyFuzzer() { deInit(); }
+ bool init();
+ void process(const uint8_t *data, size_t size);
+
+private:
+ void deInit();
+ void invokeDrmPlugin(const uint8_t *data, size_t size);
+ void invokeCryptoPlugin(const uint8_t *data);
+ void invokeDrm(const uint8_t *data, size_t size);
+ void invokeCrypto(const uint8_t *data);
+ void invokeDrmDecryptEncryptAPI(const uint8_t *data, size_t size);
+ bool invokeDrmFactory();
+ bool invokeCryptoFactory();
+ void invokeDrmV1_4API();
+ void invokeDrmSetAlgorithmAPI();
+ void invokeDrmPropertyAPI();
+ void invokeDrmSecureStopAPI();
+ void invokeDrmOfflineLicenseAPI(const uint8_t *data, size_t size);
+ SessionId getSessionId();
+ SecureStopRelease makeSecureRelease(const SecureStop &stop);
+ sp<IDrmFactory> mDrmFactory = nullptr;
+ sp<ICryptoFactory> mCryptoFactory = nullptr;
+ sp<IDrmPlugin> mDrmPlugin = nullptr;
+ sp<drm::V1_1::IDrmPlugin> mDrmPluginV1_1 = nullptr;
+ sp<drm::V1_2::IDrmPlugin> mDrmPluginV1_2 = nullptr;
+ sp<drm::V1_4::IDrmPlugin> mDrmPluginV1_4 = nullptr;
+ sp<drm::V1_4::ICryptoPlugin> mCryptoPluginV1_4 = nullptr;
+ sp<ICryptoPlugin> mCryptoPlugin = nullptr;
+ FuzzedDataProvider *mFDP = nullptr;
+ SessionId mSessionId = {};
+ SessionId mSessionIdV1 = {};
+};
+
+void ClearKeyFuzzer::deInit() {
+ if (mDrmPluginV1_1) {
+ mDrmPluginV1_1->closeSession(mSessionIdV1);
+ }
+ if (mDrmPluginV1_2) {
+ mDrmPluginV1_2->closeSession(mSessionId);
+ }
+ mDrmFactory.clear();
+ mCryptoFactory.clear();
+ mDrmPlugin.clear();
+ mDrmPluginV1_1.clear();
+ mDrmPluginV1_2.clear();
+ mDrmPluginV1_4.clear();
+ mCryptoPlugin.clear();
+ mCryptoPluginV1_4.clear();
+ mSessionId = {};
+ mSessionIdV1 = {};
+}
+
+void ClearKeyFuzzer::invokeDrmV1_4API() {
+ mDrmPluginV1_4->requiresSecureDecoderDefault(
+ getValueFromArray(mFDP, kMimeType));
+ mDrmPluginV1_4->requiresSecureDecoder(
+ getValueFromArray(mFDP, kMimeType),
+ getValueFromArray(mFDP, kSecurityLevel));
+ mDrmPluginV1_4->setPlaybackId(
+ mSessionId, mFDP->ConsumeRandomLengthString(kMaxStringLength).c_str());
+ drm::V1_4::IDrmPlugin::getLogMessages_cb cb =
+ [&]([[maybe_unused]] drm::V1_4::Status status,
+ [[maybe_unused]] hidl_vec<drm::V1_4::LogMessage> logs) {};
+ mDrmPluginV1_4->getLogMessages(cb);
+}
+
+void ClearKeyFuzzer::invokeDrmSetAlgorithmAPI() {
+ const hidl_string cipherAlgo =
+ mFDP->ConsumeBool()
+ ? mFDP->ConsumeRandomLengthString(kMaxStringLength).c_str()
+ : hidl_string(kCipherAlgorithm[mFDP->ConsumeBool()]);
+ mDrmPluginV1_2->setCipherAlgorithm(mSessionId, cipherAlgo);
+
+ const hidl_string macAlgo =
+ mFDP->ConsumeBool()
+ ? mFDP->ConsumeRandomLengthString(kMaxStringLength).c_str()
+ : hidl_string(kMacAlgorithm[mFDP->ConsumeBool()]);
+ mDrmPluginV1_2->setMacAlgorithm(mSessionId, macAlgo);
+}
+
+void ClearKeyFuzzer::invokeDrmPropertyAPI() {
+ mDrmPluginV1_2->setPropertyString(
+ hidl_string(getValueFromArray(mFDP, kProperty)), hidl_string("value"));
+
+ hidl_string stringValue;
+ mDrmPluginV1_2->getPropertyString(
+ getValueFromArray(mFDP, kProperty),
+ [&](Status status, const hidl_string &hValue) {
+ if (status == Status::OK) {
+ stringValue = hValue;
+ }
+ });
+
+ hidl_vec<uint8_t> value = {};
+ mDrmPluginV1_2->setPropertyByteArray(
+ hidl_string(getValueFromArray(mFDP, kProperty)), value);
+
+ hidl_vec<uint8_t> byteValue;
+ mDrmPluginV1_2->getPropertyByteArray(
+ getValueFromArray(mFDP, kProperty),
+ [&](Status status, const hidl_vec<uint8_t> &hValue) {
+ if (status == Status::OK) {
+ byteValue = hValue;
+ }
+ });
+}
+
+SessionId ClearKeyFuzzer::getSessionId() {
+ SessionId emptySessionId = {};
+ return mFDP->ConsumeBool() ? mSessionId : emptySessionId;
+}
+
+void ClearKeyFuzzer::invokeDrmDecryptEncryptAPI(const uint8_t *data,
+ size_t size) {
+ uint32_t currSessions, maximumSessions;
+ mDrmPluginV1_2->getNumberOfSessions(
+ [&](Status status, uint32_t hCurrentSessions, uint32_t hMaxSessions) {
+ if (status == Status::OK) {
+ currSessions = hCurrentSessions;
+ maximumSessions = hMaxSessions;
+ }
+ });
+
+ HdcpLevel connected, maximum;
+ mDrmPluginV1_2->getHdcpLevels([&](Status status,
+ const HdcpLevel &hConnectedLevel,
+ const HdcpLevel &hMaxLevel) {
+ if (status == Status::OK) {
+ connected = hConnectedLevel;
+ maximum = hMaxLevel;
+ }
+ });
+
+ drm::V1_2::HdcpLevel connectedV1_2, maximumV1_2;
+ mDrmPluginV1_2->getHdcpLevels_1_2(
+ [&](drm::V1_2::Status status, const drm::V1_2::HdcpLevel &connectedLevel,
+ const drm::V1_2::HdcpLevel &maxLevel) {
+ if (status == drm::V1_2::Status::OK) {
+ connectedV1_2 = connectedLevel;
+ maximumV1_2 = maxLevel;
+ }
+ });
+
+ SecurityLevel securityLevel;
+ mDrmPluginV1_2->getSecurityLevel(mSessionId,
+ [&](Status status, SecurityLevel hLevel) {
+ if (status == Status::OK) {
+ securityLevel = hLevel;
+ }
+ });
+
+ hidl_vec<DrmMetricGroup> metrics;
+ mDrmPluginV1_2->getMetrics(
+ [&](Status status, hidl_vec<DrmMetricGroup> hMetricGroups) {
+ if (status == Status::OK) {
+ metrics = hMetricGroups;
+ }
+ });
+
+ hidl_string certificateType;
+ hidl_string certificateAuthority;
+ mDrmPluginV1_2->getProvisionRequest(certificateType, certificateAuthority,
+ [&]([[maybe_unused]] Status status,
+ const hidl_vec<uint8_t> &,
+ const hidl_string &) {});
+
+ mDrmPluginV1_2->getProvisionRequest_1_2(
+ certificateType, certificateAuthority,
+ [&]([[maybe_unused]] drm::V1_2::Status status, const hidl_vec<uint8_t> &,
+ const hidl_string &) {});
+
+ hidl_vec<uint8_t> response;
+ mDrmPluginV1_2->provideProvisionResponse(
+ response, [&]([[maybe_unused]] Status status, const hidl_vec<uint8_t> &,
+ const hidl_vec<uint8_t> &) {});
+
+ hidl_vec<uint8_t> initData = {};
+ if (mFDP->ConsumeBool()) {
+ initData = validInitData;
+ } else {
+ initData.setToExternal(const_cast<uint8_t *>(data), kAESBlockSize);
+ }
+ hidl_string mimeType = getValueFromArray(mFDP, kMimeType);
+ KeyType keyType = mFDP->ConsumeBool()
+ ? static_cast<KeyType>(mFDP->ConsumeIntegral<size_t>())
+ : getValueFromArray(mFDP, kKeyType);
+ KeyedVector optionalParameters;
+ mDrmPluginV1_2->getKeyRequest_1_2(
+ mSessionId, initData, mimeType, keyType, optionalParameters,
+ [&]([[maybe_unused]] drm::V1_2::Status status, const hidl_vec<uint8_t> &,
+ drm::V1_1::KeyRequestType, const hidl_string &) {});
+ mDrmPluginV1_1->getKeyRequest_1_1(
+ mSessionIdV1, initData, mimeType, keyType, optionalParameters,
+ [&]([[maybe_unused]] drm::V1_0::Status status, const hidl_vec<uint8_t> &,
+ drm::V1_1::KeyRequestType, const hidl_string &) {});
+ hidl_vec<uint8_t> emptyInitData = {};
+ mDrmPlugin->getKeyRequest(
+ mSessionId, mFDP->ConsumeBool() ? initData : emptyInitData, mimeType,
+ keyType, optionalParameters,
+ [&]([[maybe_unused]] drm::V1_0::Status status, const hidl_vec<uint8_t> &,
+ drm::V1_0::KeyRequestType, const hidl_string &) {});
+
+ hidl_vec<uint8_t> keyResponse = {};
+ if (mFDP->ConsumeBool()) {
+ keyResponse = validKeyResponse;
+ } else {
+ keyResponse.setToExternal(const_cast<uint8_t *>(data), size);
+ }
+ hidl_vec<uint8_t> keySetId;
+ hidl_vec<uint8_t> emptyKeyResponse = {};
+ mDrmPluginV1_2->provideKeyResponse(
+ getSessionId(), mFDP->ConsumeBool() ? keyResponse : emptyKeyResponse,
+ [&](Status status, const hidl_vec<uint8_t> &hKeySetId) {
+ if (status == Status::OK) {
+ keySetId = hKeySetId;
+ }
+ });
+
+ mDrmPluginV1_2->restoreKeys(getSessionId(), keySetId);
+
+ mDrmPluginV1_2->queryKeyStatus(
+ getSessionId(),
+ [&]([[maybe_unused]] Status status, KeyedVector /* info */) {});
+
+ hidl_vec<uint8_t> keyId, input, iv;
+ keyId.setToExternal(const_cast<uint8_t *>(data), size);
+ input.setToExternal(const_cast<uint8_t *>(data), size);
+ iv.setToExternal(const_cast<uint8_t *>(data), size);
+ mDrmPluginV1_2->encrypt(
+ getSessionId(), keyId, input, iv,
+ [&]([[maybe_unused]] Status status, const hidl_vec<uint8_t> &) {});
+
+ mDrmPluginV1_2->decrypt(
+ getSessionId(), keyId, input, iv,
+ [&]([[maybe_unused]] Status status, const hidl_vec<uint8_t> &) {});
+
+ hidl_vec<uint8_t> message;
+ message.setToExternal(const_cast<uint8_t *>(data), size);
+ mDrmPluginV1_2->sign(
+ getSessionId(), keyId, message,
+ [&]([[maybe_unused]] Status status, const hidl_vec<uint8_t> &) {});
+
+ hidl_vec<uint8_t> signature;
+ signature.setToExternal(const_cast<uint8_t *>(data), size);
+ mDrmPluginV1_2->verify(getSessionId(), keyId, message, signature,
+ [&]([[maybe_unused]] Status status, bool) {});
+
+ hidl_vec<uint8_t> wrappedKey;
+ signature.setToExternal(const_cast<uint8_t *>(data), size);
+ mDrmPluginV1_2->signRSA(
+ getSessionId(), kRSAAlgorithm[mFDP->ConsumeBool()], message, wrappedKey,
+ [&]([[maybe_unused]] Status status, const hidl_vec<uint8_t> &) {});
+
+ mDrmPluginV1_2->removeKeys(getSessionId());
+}
+
+/**
+ * Helper function to create a secure release message for
+ * a secure stop. The clearkey secure stop release format
+ * is just a count followed by the secure stop opaque data.
+ */
+SecureStopRelease ClearKeyFuzzer::makeSecureRelease(const SecureStop &stop) {
+ std::vector<uint8_t> stopData = stop.opaqueData;
+ std::vector<uint8_t> buffer;
+ std::string count = "0001";
+
+ auto it = buffer.insert(buffer.begin(), count.begin(), count.end());
+ buffer.insert(it + count.size(), stopData.begin(), stopData.end());
+ SecureStopRelease release = {.opaqueData = hidl_vec<uint8_t>(buffer)};
+ return release;
+}
+
+void ClearKeyFuzzer::invokeDrmSecureStopAPI() {
+ SecureStopId ssid;
+ mDrmPluginV1_2->getSecureStop(
+ ssid, [&]([[maybe_unused]] Status status, const SecureStop &) {});
+
+ mDrmPluginV1_2->getSecureStopIds(
+ [&]([[maybe_unused]] Status status,
+ [[maybe_unused]] const hidl_vec<SecureStopId> &secureStopIds) {});
+
+ SecureStopRelease release;
+ mDrmPluginV1_2->getSecureStops(
+ [&]([[maybe_unused]] Status status, const hidl_vec<SecureStop> &stops) {
+ if (stops.size() > 0) {
+ release = makeSecureRelease(
+ stops[mFDP->ConsumeIntegralInRange<size_t>(0, stops.size() - 1)]);
+ }
+ });
+
+ mDrmPluginV1_2->releaseSecureStops(release);
+
+ mDrmPluginV1_2->removeSecureStop(ssid);
+
+ mDrmPluginV1_2->removeAllSecureStops();
+
+ mDrmPluginV1_2->releaseSecureStop(ssid);
+
+ mDrmPluginV1_2->releaseAllSecureStops();
+}
+
+void ClearKeyFuzzer::invokeDrmOfflineLicenseAPI(const uint8_t *data,
+ size_t size) {
+ hidl_vec<KeySetId> keySetIds = {};
+ mDrmPluginV1_2->getOfflineLicenseKeySetIds(
+ [&](Status status, const hidl_vec<KeySetId> &hKeySetIds) {
+ if (status == Status::OK) {
+ keySetIds = hKeySetIds;
+ }
+ });
+
+ OfflineLicenseState licenseState;
+ KeySetId keySetId = {};
+ if (keySetIds.size() > 0) {
+ keySetId = keySetIds[mFDP->ConsumeIntegralInRange<size_t>(
+ 0, keySetIds.size() - 1)];
+ } else {
+ keySetId.setToExternal(const_cast<uint8_t *>(data), size);
+ }
+ mDrmPluginV1_2->getOfflineLicenseState(
+ keySetId, [&](Status status, OfflineLicenseState hLicenseState) {
+ if (status == Status::OK) {
+ licenseState = hLicenseState;
+ }
+ });
+
+ mDrmPluginV1_2->removeOfflineLicense(keySetId);
+}
+
+void ClearKeyFuzzer::invokeDrmPlugin(const uint8_t *data, size_t size) {
+ SecurityLevel secLevel =
+ mFDP->ConsumeBool()
+ ? getValueFromArray(mFDP, kSecurityLevel)
+ : static_cast<SecurityLevel>(mFDP->ConsumeIntegral<uint32_t>());
+ mDrmPluginV1_1->openSession_1_1(
+ secLevel, [&]([[maybe_unused]] Status status, const SessionId &id) {
+ mSessionIdV1 = id;
+ });
+ mDrmPluginV1_2->openSession([&]([[maybe_unused]] Status status,
+ const SessionId &id) { mSessionId = id; });
+
+ sp<TestDrmPluginListener> listener = new TestDrmPluginListener();
+ mDrmPluginV1_2->setListener(listener);
+ const hidl_vec<KeyStatus> keyStatusList = {
+ {{1}, KeyStatusType::USABLE},
+ {{2}, KeyStatusType::EXPIRED},
+ {{3}, KeyStatusType::OUTPUTNOTALLOWED},
+ {{4}, KeyStatusType::STATUSPENDING},
+ {{5}, KeyStatusType::INTERNALERROR},
+ };
+ mDrmPluginV1_2->sendKeysChange(mSessionId, keyStatusList, true);
+
+ invokeDrmV1_4API();
+ invokeDrmSetAlgorithmAPI();
+ invokeDrmPropertyAPI();
+ invokeDrmDecryptEncryptAPI(data, size);
+ invokeDrmSecureStopAPI();
+ invokeDrmOfflineLicenseAPI(data, size);
+}
+
+void ClearKeyFuzzer::invokeCryptoPlugin(const uint8_t *data) {
+ mCryptoPlugin->requiresSecureDecoderComponent(
+ getValueFromArray(mFDP, kMimeType));
+
+ const uint32_t width = mFDP->ConsumeIntegral<uint32_t>();
+ const uint32_t height = mFDP->ConsumeIntegral<uint32_t>();
+ mCryptoPlugin->notifyResolution(width, height);
+
+ mCryptoPlugin->setMediaDrmSession(mSessionId);
+
+ size_t totalSize = 0;
+ const size_t numSubSamples =
+ mFDP->ConsumeIntegralInRange<size_t>(1, kMaxSubSamples);
+
+ const Pattern pattern = {0, 0};
+ hidl_vec<SubSample> subSamples;
+ subSamples.resize(numSubSamples);
+
+ for (size_t i = 0; i < numSubSamples; ++i) {
+ const uint32_t clearBytes =
+ mFDP->ConsumeIntegralInRange<uint32_t>(0, kMaxNumBytes);
+ const uint32_t encryptedBytes =
+ mFDP->ConsumeIntegralInRange<uint32_t>(0, kMaxNumBytes);
+ subSamples[i].numBytesOfClearData = clearBytes;
+ subSamples[i].numBytesOfEncryptedData = encryptedBytes;
+ totalSize += subSamples[i].numBytesOfClearData;
+ totalSize += subSamples[i].numBytesOfEncryptedData;
+ }
+
+ // The first totalSize bytes of shared memory is the encrypted
+ // input, the second totalSize bytes is the decrypted output.
+ size_t memoryBytes = totalSize * 2;
+
+ sp<IAllocator> ashmemAllocator = IAllocator::getService("ashmem");
+ if (!ashmemAllocator.get()) {
+ return;
+ }
+
+ hidl_memory hidlMemory;
+ ashmemAllocator->allocate(memoryBytes, [&]([[maybe_unused]] bool success,
+ const hidl_memory &memory) {
+ mCryptoPlugin->setSharedBufferBase(memory, kSegmentIndex);
+ hidlMemory = memory;
+ });
+
+ sp<IMemory> mappedMemory = mapMemory(hidlMemory);
+ if (!mappedMemory.get()) {
+ return;
+ }
+ mCryptoPlugin->setSharedBufferBase(hidlMemory, kSegmentIndex);
+
+ uint32_t srcBufferId =
+ mFDP->ConsumeBool() ? kSegmentIndex : mFDP->ConsumeIntegral<uint32_t>();
+ const SharedBuffer sourceBuffer = {
+ .bufferId = srcBufferId, .offset = 0, .size = totalSize};
+
+ BufferType type = mFDP->ConsumeBool() ? BufferType::SHARED_MEMORY
+ : BufferType::NATIVE_HANDLE;
+ uint32_t destBufferId =
+ mFDP->ConsumeBool() ? kSegmentIndex : mFDP->ConsumeIntegral<uint32_t>();
+ const DestinationBuffer destBuffer = {
+ .type = type,
+ {.bufferId = destBufferId, .offset = totalSize, .size = totalSize},
+ .secureMemory = nullptr};
+
+ const uint64_t offset = 0;
+ uint32_t bytesWritten = 0;
+ hidl_array<uint8_t, kAESBlockSize> keyId =
+ hidl_array<uint8_t, kAESBlockSize>(data);
+ hidl_array<uint8_t, kAESBlockSize> iv =
+ hidl_array<uint8_t, kAESBlockSize>(data);
+ Mode mode = getValueFromArray(mFDP, kCryptoMode);
+ mCryptoPlugin->decrypt(
+ mFDP->ConsumeBool(), keyId, iv, mode, pattern, subSamples, sourceBuffer,
+ offset, destBuffer,
+ [&]([[maybe_unused]] Status status, uint32_t count,
+ [[maybe_unused]] string detailedError) { bytesWritten = count; });
+ drm::V1_4::IDrmPlugin::getLogMessages_cb cb =
+ [&]([[maybe_unused]] drm::V1_4::Status status,
+ [[maybe_unused]] hidl_vec<drm::V1_4::LogMessage> logs) {};
+ mCryptoPluginV1_4->getLogMessages(cb);
+}
+
+bool ClearKeyFuzzer::invokeDrmFactory() {
+ hidl_string packageName(
+ mFDP->ConsumeRandomLengthString(kMaxStringLength).c_str());
+ hidl_string mimeType(getValueFromArray(mFDP, kMimeType));
+ SecurityLevel securityLevel =
+ mFDP->ConsumeBool()
+ ? getValueFromArray(mFDP, kSecurityLevel)
+ : static_cast<SecurityLevel>(mFDP->ConsumeIntegral<uint32_t>());
+ const hidl_array<uint8_t, 16> uuid =
+ mFDP->ConsumeBool() ? kClearKeyUUID : kInvalidUUID;
+ mDrmFactory->isCryptoSchemeSupported_1_2(uuid, mimeType, securityLevel);
+ mDrmFactory->createPlugin(
+ uuid, packageName, [&](Status status, const sp<IDrmPlugin> &plugin) {
+ if (status == Status::OK) {
+ mDrmPlugin = plugin.get();
+ mDrmPluginV1_1 = drm::V1_1::IDrmPlugin::castFrom(mDrmPlugin);
+ mDrmPluginV1_2 = drm::V1_2::IDrmPlugin::castFrom(mDrmPlugin);
+ mDrmPluginV1_4 = drm::V1_4::IDrmPlugin::castFrom(mDrmPlugin);
+ }
+ });
+
+ std::vector<hidl_array<uint8_t, 16>> supportedSchemes;
+ mDrmFactory->getSupportedCryptoSchemes(
+ [&](const hidl_vec<hidl_array<uint8_t, 16>> &schemes) {
+ for (const auto &scheme : schemes) {
+ supportedSchemes.push_back(scheme);
+ }
+ });
+
+ if (!(mDrmPlugin && mDrmPluginV1_1 && mDrmPluginV1_2 && mDrmPluginV1_4)) {
+ return false;
+ }
+ return true;
+}
+
+bool ClearKeyFuzzer::invokeCryptoFactory() {
+ const hidl_array<uint8_t, 16> uuid =
+ mFDP->ConsumeBool() ? kClearKeyUUID : kInvalidUUID;
+ mCryptoFactory->createPlugin(
+ uuid, mSessionId, [this](Status status, const sp<ICryptoPlugin> &plugin) {
+ if (status == Status::OK) {
+ mCryptoPlugin = plugin;
+ mCryptoPluginV1_4 = drm::V1_4::ICryptoPlugin::castFrom(mCryptoPlugin);
+ }
+ });
+
+ if (!mCryptoPlugin && !mCryptoPluginV1_4) {
+ return false;
+ }
+ return true;
+}
+
+void ClearKeyFuzzer::invokeDrm(const uint8_t *data, size_t size) {
+ if (!invokeDrmFactory()) {
+ return;
+ }
+ invokeDrmPlugin(data, size);
+}
+
+void ClearKeyFuzzer::invokeCrypto(const uint8_t *data) {
+ if (!invokeCryptoFactory()) {
+ return;
+ }
+ invokeCryptoPlugin(data);
+}
+
+void ClearKeyFuzzer::process(const uint8_t *data, size_t size) {
+ mFDP = new FuzzedDataProvider(data, size);
+ invokeDrm(data, size);
+ invokeCrypto(data);
+ delete mFDP;
+}
+
+bool ClearKeyFuzzer::init() {
+ mCryptoFactory =
+ android::hardware::drm::V1_4::clearkey::createCryptoFactory();
+ mDrmFactory = android::hardware::drm::V1_4::clearkey::createDrmFactory();
+ if (!mDrmFactory && !mCryptoFactory) {
+ return false;
+ }
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (size < kAESBlockSize) {
+ return 0;
+ }
+ ClearKeyFuzzer clearKeyFuzzer;
+ if (clearKeyFuzzer.init()) {
+ clearKeyFuzzer.process(data, size);
+ }
+ return 0;
+}
diff --git a/media/codec2/TEST_MAPPING b/media/codec2/TEST_MAPPING
index 16cb323..c6728c8 100644
--- a/media/codec2/TEST_MAPPING
+++ b/media/codec2/TEST_MAPPING
@@ -35,6 +35,17 @@
"exclude-filter": "android.media.audio.cts.AudioRecordTest"
}
]
+ },
+ {
+ "name": "CtsMediaPlayerTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
+ }
+ ]
}
]
}
diff --git a/media/codec2/components/gav1/C2SoftGav1Dec.cpp b/media/codec2/components/gav1/C2SoftGav1Dec.cpp
index 7bd3358..475d863 100644
--- a/media/codec2/components/gav1/C2SoftGav1Dec.cpp
+++ b/media/codec2/components/gav1/C2SoftGav1Dec.cpp
@@ -20,6 +20,7 @@
#include <C2Debug.h>
#include <C2PlatformSupport.h>
+#include <Codec2BufferUtils.h>
#include <Codec2Mapper.h>
#include <SimpleC2Interface.h>
#include <log/log.h>
@@ -338,6 +339,7 @@
std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)),
mIntf(intfImpl),
mCodecCtx(nullptr) {
+ mIsFormatR10G10B10A2Supported = IsFormatR10G10B10A2SupportedForLegacyRendering();
gettimeofday(&mTimeStart, nullptr);
gettimeofday(&mTimeEnd, nullptr);
}
@@ -790,7 +792,14 @@
work->workletsProcessed = 1u;
return false;
}
- format = HAL_PIXEL_FORMAT_RGBA_1010102;
+ // TODO (b/201787956) For devices that do not support HAL_PIXEL_FORMAT_RGBA_1010102,
+ // HAL_PIXEL_FORMAT_YV12 is used as a temporary work around.
+ if (!mIsFormatR10G10B10A2Supported) {
+ ALOGE("HAL_PIXEL_FORMAT_RGBA_1010102 isn't supported");
+ format = HAL_PIXEL_FORMAT_YV12;
+ } else {
+ format = HAL_PIXEL_FORMAT_RGBA_1010102;
+ }
}
}
C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE};
diff --git a/media/codec2/components/gav1/C2SoftGav1Dec.h b/media/codec2/components/gav1/C2SoftGav1Dec.h
index 134fa0d..f82992d 100644
--- a/media/codec2/components/gav1/C2SoftGav1Dec.h
+++ b/media/codec2/components/gav1/C2SoftGav1Dec.h
@@ -82,6 +82,7 @@
struct timeval mTimeStart; // Time at the start of decode()
struct timeval mTimeEnd; // Time at the end of decode()
+ bool mIsFormatR10G10B10A2Supported;
bool initDecoder();
void getVuiParams(const libgav1::DecoderBuffer *buffer);
diff --git a/media/codec2/components/vpx/C2SoftVpxDec.cpp b/media/codec2/components/vpx/C2SoftVpxDec.cpp
index 45e2ca8..2da9d5b 100644
--- a/media/codec2/components/vpx/C2SoftVpxDec.cpp
+++ b/media/codec2/components/vpx/C2SoftVpxDec.cpp
@@ -25,6 +25,7 @@
#include <C2Debug.h>
#include <C2PlatformSupport.h>
+#include <Codec2BufferUtils.h>
#include <SimpleC2Interface.h>
#include "C2SoftVpxDec.h"
@@ -351,6 +352,7 @@
mCodecCtx(nullptr),
mCoreCount(1),
mQueue(new Mutexed<ConversionQueue>) {
+ mIsFormatR10G10B10A2Supported = IsFormatR10G10B10A2SupportedForLegacyRendering();
}
C2SoftVpxDec::~C2SoftVpxDec() {
@@ -804,7 +806,14 @@
if (defaultColorAspects->primaries == C2Color::PRIMARIES_BT2020 &&
defaultColorAspects->matrix == C2Color::MATRIX_BT2020 &&
defaultColorAspects->transfer == C2Color::TRANSFER_ST2084) {
- format = HAL_PIXEL_FORMAT_RGBA_1010102;
+ // TODO (b/201787956) For devices that do not support HAL_PIXEL_FORMAT_RGBA_1010102,
+ // HAL_PIXEL_FORMAT_YV12 is used as a temporary work around.
+ if (!mIsFormatR10G10B10A2Supported) {
+ ALOGE("HAL_PIXEL_FORMAT_RGBA_1010102 isn't supported");
+ format = HAL_PIXEL_FORMAT_YV12;
+ } else {
+ format = HAL_PIXEL_FORMAT_RGBA_1010102;
+ }
}
}
C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
diff --git a/media/codec2/components/vpx/C2SoftVpxDec.h b/media/codec2/components/vpx/C2SoftVpxDec.h
index 2065165..ade162d 100644
--- a/media/codec2/components/vpx/C2SoftVpxDec.h
+++ b/media/codec2/components/vpx/C2SoftVpxDec.h
@@ -80,7 +80,7 @@
};
std::shared_ptr<Mutexed<ConversionQueue>> mQueue;
std::vector<sp<ConverterThread>> mConverterThreads;
-
+ bool mIsFormatR10G10B10A2Supported;
status_t initDecoder();
status_t destroyDecoder();
void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work,
diff --git a/media/codec2/sfplugin/utils/Codec2BufferUtils.cpp b/media/codec2/sfplugin/utils/Codec2BufferUtils.cpp
index 5f87c66..2213001 100644
--- a/media/codec2/sfplugin/utils/Codec2BufferUtils.cpp
+++ b/media/codec2/sfplugin/utils/Codec2BufferUtils.cpp
@@ -118,6 +118,22 @@
} // namespace
+bool IsFormatR10G10B10A2SupportedForLegacyRendering() {
+ const AHardwareBuffer_Desc desc = {
+ .width = 320,
+ .height = 240,
+ .format = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM,
+ .layers = 1,
+ .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
+ .stride = 0,
+ .rfu0 = 0,
+ .rfu1 = 0,
+ };
+
+ return AHardwareBuffer_isSupported(&desc);
+}
+
status_t ImageCopy(uint8_t *imgBase, const MediaImage2 *img, const C2GraphicView &view) {
if (view.crop().width != img->mWidth || view.crop().height != img->mHeight) {
return BAD_VALUE;
diff --git a/media/codec2/sfplugin/utils/Codec2BufferUtils.h b/media/codec2/sfplugin/utils/Codec2BufferUtils.h
index 9fa642d..c4651a4 100644
--- a/media/codec2/sfplugin/utils/Codec2BufferUtils.h
+++ b/media/codec2/sfplugin/utils/Codec2BufferUtils.h
@@ -27,6 +27,11 @@
namespace android {
/**
+ * Check if R10G10B10A2 is supported in legacy rendering path that involves GPU
+ */
+bool IsFormatR10G10B10A2SupportedForLegacyRendering();
+
+/**
* Converts an RGB view to planar YUV 420 media image.
*
* \param dstY pointer to media image buffer
diff --git a/media/libaaudio/scripts/measure_device_power.py b/media/libaaudio/scripts/measure_device_power.py
new file mode 100755
index 0000000..9603f88
--- /dev/null
+++ b/media/libaaudio/scripts/measure_device_power.py
@@ -0,0 +1,272 @@
+#!/usr/bin/python3
+"""
+ * Copyright (C) 2021 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.
+"""
+
+'''
+Measure CPU related power on Pixel 6 or later devices using ODPM,
+the On Device Power Measurement tool.
+Generate a CSV report for putting in a spreadsheet
+'''
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+import time
+
+# defaults
+PRE_DELAY_SECONDS = 0.5 # time to sleep before command to avoid adb unroot error
+DEFAULT_NUM_ITERATIONS = 5
+DEFAULT_FILE_NAME = 'energy_commands.txt'
+
+'''
+Default rail assignments
+philburk-macbookpro3:expt philburk$ adb shell cat /sys/bus/iio/devices/iio\:device0/energy_value
+t=349894
+CH0(T=349894)[S10M_VDD_TPU], 5578756
+CH1(T=349894)[VSYS_PWR_MODEM], 29110940
+CH2(T=349894)[VSYS_PWR_RFFE], 3166046
+CH3(T=349894)[S2M_VDD_CPUCL2], 30203502
+CH4(T=349894)[S3M_VDD_CPUCL1], 23377533
+CH5(T=349894)[S4M_VDD_CPUCL0], 46356942
+CH6(T=349894)[S5M_VDD_INT], 10771876
+CH7(T=349894)[S1M_VDD_MIF], 21091363
+philburk-macbookpro3:expt philburk$ adb shell cat /sys/bus/iio/devices/iio\:device1/energy_value
+t=359458
+CH0(T=359458)[VSYS_PWR_WLAN_BT], 45993209
+CH1(T=359458)[L2S_VDD_AOC_RET], 2822928
+CH2(T=359458)[S9S_VDD_AOC], 6923706
+CH3(T=359458)[S5S_VDDQ_MEM], 4658202
+CH4(T=359458)[S10S_VDD2L], 5506273
+CH5(T=359458)[S4S_VDD2H_MEM], 14254574
+CH6(T=359458)[S2S_VDD_G3D], 5315420
+CH7(T=359458)[VSYS_PWR_DISPLAY], 81221665
+'''
+
+'''
+LDO2M(L2M_ALIVE):DDR -> DRAM Array Core Power
+BUCK4S(S4S_VDD2H_MEM):DDR -> Normal operation data and control path logic circuits
+BUCK5S(S5S_VDDQ_MEM):DDR -> LPDDR I/O interface
+BUCK10S(S10S_VDD2L):DDR -> DVFSC (1600Mbps or lower) operation data and control path logic circuits
+BUCK1M (S1M_VDD_MIF): SoC side Memory InterFace and Controller
+'''
+
+# Map between rail name and human readable name.
+ENERGY_DICTIONARY = { \
+ 'S4M_VDD_CPUCL0': 'CPU0', \
+ 'S3M_VDD_CPUCL1': 'CPU1', \
+ 'S2M_VDD_CPUCL2': 'CPU2', \
+ 'S1M_VDD_MIF': 'MIF', \
+ 'L2M_ALIVE': 'DDRAC', \
+ 'S4S_VDD2H_MEM': 'DDRNO', \
+ 'S10S_VDD2L': 'DDR16', \
+ 'S5S_VDDQ_MEM': 'DDRIO', \
+ 'VSYS_PWR_DISPLAY': 'SCREEN'}
+
+SORTED_ENERGY_LIST = sorted(ENERGY_DICTIONARY, key=ENERGY_DICTIONARY.get)
+
+# Sometimes "adb unroot" returns 1!
+# So try several times.
+# @return 0 on success
+def adbUnroot():
+ returnCode = 1
+ count = 0
+ limit = 5
+ while count < limit and returnCode != 0:
+ print(('Try to adb unroot {} of {}'.format(count, limit)))
+ subprocess.call(["adb", "wait-for-device"])
+ time.sleep(PRE_DELAY_SECONDS)
+ returnCode = subprocess.call(["adb", "unroot"])
+ print(('returnCode = {}'.format(returnCode)))
+ count += 1
+ return returnCode
+
+# @param commandString String containing shell command
+# @return Both the stdout and stderr of the commands run
+def runCommand(commandString):
+ print(commandString)
+ if commandString == "adb unroot":
+ result = adbUnroot()
+ else:
+ commandArray = commandString.split(' ')
+ result = subprocess.run(commandArray, check=True, capture_output=True).stdout
+ return result
+
+# @param commandString String containing ADB command
+# @return Both the stdout and stderr of the commands run
+def adbCommand(commandString):
+ if commandString == "unroot":
+ result = adbUnroot()
+ else:
+ print(("adb " + commandString))
+ commandArray = ["adb"] + commandString.split(' ')
+ subprocess.call(["adb", "wait-for-device"])
+ result = subprocess.run(commandArray, check=True, capture_output=True).stdout
+ return result
+
+# Parse a line that looks like "CH3(T=10697635)[S2M_VDD_CPUCL2], 116655335"
+# Use S2M_VDD_CPUCL2 as the tag and set value to the number
+# in the report dictionary.
+def parseEnergyValue(string):
+ return tuple(re.split('\[|\], +', string)[1:])
+
+# Read accumulated energy into a dictionary.
+def measureEnergyForDevice(deviceIndex, report):
+ # print("measureEnergyForDevice " + str(deviceIndex))
+ tableBytes = adbCommand( \
+ 'shell cat /sys/bus/iio/devices/iio\:device{}/energy_value'\
+ .format(deviceIndex))
+ table = tableBytes.decode("utf-8")
+ # print(table)
+ for count, line in enumerate(table.splitlines()):
+ if count > 0:
+ tagEnergy = parseEnergyValue(line)
+ report[tagEnergy[0]] = int(tagEnergy[1].strip())
+ # print(report)
+
+def measureEnergyOnce():
+ adbCommand("root")
+ report = {}
+ d0 = measureEnergyForDevice(0, report)
+ d1 = measureEnergyForDevice(1, report)
+ adbUnroot()
+ return report
+
+# Subtract numeric values for matching keys.
+def subtractReports(A, B):
+ return {x: A[x] - B[x] for x in A if x in B}
+
+# Add numeric values for matching keys.
+def addReports(A, B):
+ return {x: A[x] + B[x] for x in A if x in B}
+
+# Divide numeric values by divisor.
+# @return Modified copy of report.
+def divideReport(report, divisor):
+ return {key: val / divisor for key, val in list(report.items())}
+
+# Generate a dictionary that is the difference between two measurements over time.
+def measureEnergyOverTime(duration):
+ report1 = measureEnergyOnce()
+ print(("Measure energy for " + str(duration) + " seconds."))
+ time.sleep(duration)
+ report2 = measureEnergyOnce()
+ return subtractReports(report2, report1)
+
+# Generate a CSV string containing the human readable headers.
+def formatEnergyHeader():
+ header = ""
+ for tag in SORTED_ENERGY_LIST:
+ header += ENERGY_DICTIONARY[tag] + ", "
+ return header
+
+# Generate a CSV string containing the numeric values.
+def formatEnergyData(report):
+ data = ""
+ for tag in SORTED_ENERGY_LIST:
+ if tag in list(report.keys()):
+ data += str(report[tag]) + ", "
+ else:
+ data += "-1,"
+ return data
+
+def printEnergyReport(report):
+ s = "\n"
+ s += "Values are in microWattSeconds\n"
+ s += "Report below is CSV format for pasting into a spreadsheet:\n"
+ s += formatEnergyHeader() + "\n"
+ s += formatEnergyData(report) + "\n"
+ print(s)
+
+# Generate a dictionary that is the difference between two measurements
+# before and after executing the command.
+def measureEnergyForCommand(command):
+ report1 = measureEnergyOnce()
+ print(("Measure energy for: " + command))
+ result = runCommand(command)
+ report2 = measureEnergyOnce()
+ # print(result)
+ return subtractReports(report2, report1)
+
+# Average the results of several measurements for one command.
+def averageEnergyForCommand(command, count):
+ print("=================== #0\n")
+ sumReport = measureEnergyForCommand(command)
+ for i in range(1, count):
+ print(("=================== #" + str(i) + "\n"))
+ report = measureEnergyForCommand(command)
+ sumReport = addReports(sumReport, report)
+ print(sumReport)
+ return divideReport(sumReport, count)
+
+# Parse a list of commands in a file.
+# Lines ending in "\" are continuation lines.
+# Lines beginning with "#" are comments.
+def measureEnergyForCommands(fileName):
+ finalReport = "------------------------------------\n"
+ finalReport += "comment, command, " + formatEnergyHeader() + "\n"
+ comment = ""
+ try:
+ fp = open(fileName)
+ line = fp.readline()
+ while line:
+ command = line.strip()
+ if command.endswith('\\'):
+ command = command[:-1].strip() # remove \\:
+ runCommand(command)
+ elif command.startswith("#"):
+ # ignore comment
+ print((command + "\n"))
+ comment = command
+ elif command:
+ report = averageEnergyForCommand(command, DEFAULT_NUM_ITERATIONS)
+ finalReport += comment + ", " + command + ", " + formatEnergyData(report) + "\n"
+ print(finalReport)
+ line = fp.readline()
+ finally:
+ fp.close()
+ return finalReport
+
+def main():
+ # parse command line args
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-s', '--seconds',
+ help="Measure power for N seconds. Ignore scriptFile.",
+ type=float)
+ parser.add_argument("fileName",
+ nargs = '?',
+ help="Path to file containing commands to be measured."
+ + " Default path = " + DEFAULT_FILE_NAME + "."
+ + " Lines ending in '\' are continuation lines."
+ + " Lines beginning with '#' are comments.",
+ default=DEFAULT_FILE_NAME)
+ args=parser.parse_args();
+
+ print(("seconds = " + str(args.seconds)))
+ print(("fileName = " + str(args.fileName)))
+ # Process command line
+ if args.seconds:
+ report = measureEnergyOverTime(args.seconds)
+ printEnergyReport(report)
+ else:
+ report = measureEnergyForCommands(args.fileName)
+ print(report)
+ print("Finished.\n")
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/media/libaaudio/scripts/setup_odpm_cpu_rails.sh b/media/libaaudio/scripts/setup_odpm_cpu_rails.sh
new file mode 100644
index 0000000..e9241b9
--- /dev/null
+++ b/media/libaaudio/scripts/setup_odpm_cpu_rails.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Configure ODPM rails to measure CPU specific power.
+# See go/odpm-p21-userguide
+
+adb root
+
+# LDO2M(L2M_ALIVE) - DRAM Array Core Power
+adb shell 'echo "CH0=LDO2M" > /sys/bus/iio/devices/iio\:device0/enabled_rails'
+
+# These are the defaults.
+# BUCK2M(S2M_VDD_CPUCL2):CPU(BIG)
+# adb shell 'echo "CH3=BUCK2M" > /sys/bus/iio/devices/iio\:device0/enabled_rails'
+# BUCK3M(S3M_VDD_CPUCL1):CPU(MID)
+# adb shell 'echo "CH4=BUCK3M" > /sys/bus/iio/devices/iio\:device0/enabled_rails'
+# BUCK4M(S4M_VDD_CPUCL0):CPU(LITTLE)
+# adb shell 'echo "CH5=BUCK4M" > /sys/bus/iio/devices/iio\:device0/enabled_rails'
+# BUCK1M(S1M_VDD_MIF):MIF
+# adb shell 'echo "CH7=BUCK1M" > /sys/bus/iio/devices/iio\:device0/enabled_rails'
+
+# These are default on device1.
+# BUCK5S(S5S_VDDQ_MEM):DDR
+# adb shell 'echo "CH3=BUCK5S" > /sys/bus/iio/devices/iio\:device1/enabled_rails'
+# BUCK10S(S10S_VDD2L):DDR
+# adb shell 'echo "CH4=BUCK10S" > /sys/bus/iio/devices/iio\:device1/enabled_rails'
+# BUCK4S(S4S_VDD2H_MEM):DDR
+# adb shell 'echo "CH5=BUCK4S" > /sys/bus/iio/devices/iio\:device1/enabled_rails'
+
+adb shell 'cat /sys/bus/iio/devices/iio\:device0/enabled_rails'
+adb shell 'cat /sys/bus/iio/devices/iio\:device1/enabled_rails'
+
+adb unroot
+
diff --git a/media/libaaudio/src/Android.bp b/media/libaaudio/src/Android.bp
index 38bcb7c..f50b53a 100644
--- a/media/libaaudio/src/Android.bp
+++ b/media/libaaudio/src/Android.bp
@@ -214,6 +214,7 @@
"flowgraph/MonoBlend.cpp",
"flowgraph/MonoToMultiConverter.cpp",
"flowgraph/MultiToMonoConverter.cpp",
+ "flowgraph/MultiToManyConverter.cpp",
"flowgraph/RampLinear.cpp",
"flowgraph/SampleRateConverter.cpp",
"flowgraph/SinkFloat.cpp",
diff --git a/media/libaaudio/src/client/AAudioFlowGraph.cpp b/media/libaaudio/src/client/AAudioFlowGraph.cpp
index d3e2912..d0c3238 100644
--- a/media/libaaudio/src/client/AAudioFlowGraph.cpp
+++ b/media/libaaudio/src/client/AAudioFlowGraph.cpp
@@ -21,8 +21,10 @@
#include "AAudioFlowGraph.h"
#include <flowgraph/ClipToRange.h>
+#include <flowgraph/ManyToMultiConverter.h>
#include <flowgraph/MonoBlend.h>
#include <flowgraph/MonoToMultiConverter.h>
+#include <flowgraph/MultiToManyConverter.h>
#include <flowgraph/RampLinear.h>
#include <flowgraph/SinkFloat.h>
#include <flowgraph/SinkI16.h>
@@ -39,12 +41,16 @@
int32_t sourceChannelCount,
audio_format_t sinkFormat,
int32_t sinkChannelCount,
- bool useMonoBlend) {
+ bool useMonoBlend,
+ float audioBalance,
+ bool isExclusive) {
FlowGraphPortFloatOutput *lastOutput = nullptr;
// TODO change back to ALOGD
- ALOGI("%s() source format = 0x%08x, channels = %d, sink format = 0x%08x, channels = %d",
- __func__, sourceFormat, sourceChannelCount, sinkFormat, sinkChannelCount);
+ ALOGI("%s() source format = 0x%08x, channels = %d, sink format = 0x%08x, channels = %d, "
+ "useMonoBlend = %d, audioBalance = %f, isExclusive %d",
+ __func__, sourceFormat, sourceChannelCount, sinkFormat, sinkChannelCount,
+ useMonoBlend, audioBalance, isExclusive);
switch (sourceFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
@@ -65,10 +71,11 @@
}
lastOutput = &mSource->output;
- // Apply volume as a ramp to avoid pops.
- mVolumeRamp = std::make_unique<RampLinear>(sourceChannelCount);
- lastOutput->connect(&mVolumeRamp->input);
- lastOutput = &mVolumeRamp->output;
+ if (useMonoBlend) {
+ mMonoBlend = std::make_unique<MonoBlend>(sourceChannelCount);
+ lastOutput->connect(&mMonoBlend->input);
+ lastOutput = &mMonoBlend->output;
+ }
// For a pure float graph, there is chance that the data range may be very large.
// So we should clip to a reasonable value that allows a little headroom.
@@ -78,12 +85,6 @@
lastOutput = &mClipper->output;
}
- if (useMonoBlend) {
- mMonoBlend = std::make_unique<MonoBlend>(sourceChannelCount);
- lastOutput->connect(&mMonoBlend->input);
- lastOutput = &mMonoBlend->output;
- }
-
// Expand the number of channels if required.
if (sourceChannelCount == 1 && sinkChannelCount > 1) {
mChannelConverter = std::make_unique<MonoToMultiConverter>(sinkChannelCount);
@@ -94,6 +95,26 @@
return AAUDIO_ERROR_UNIMPLEMENTED;
}
+ // Apply volume ramps for only exclusive streams.
+ if (isExclusive) {
+ // Apply volume ramps to set the left/right audio balance and target volumes.
+ // The signals will be decoupled, volume ramps will be applied, before the signals are
+ // combined again.
+ mMultiToManyConverter = std::make_unique<MultiToManyConverter>(sinkChannelCount);
+ mManyToMultiConverter = std::make_unique<ManyToMultiConverter>(sinkChannelCount);
+ lastOutput->connect(&mMultiToManyConverter->input);
+ for (int i = 0; i < sinkChannelCount; i++) {
+ mVolumeRamps.emplace_back(std::make_unique<RampLinear>(1));
+ mPanningVolumes.emplace_back(1.0f);
+ lastOutput = mMultiToManyConverter->outputs[i].get();
+ lastOutput->connect(&(mVolumeRamps[i].get()->input));
+ lastOutput = &(mVolumeRamps[i].get()->output);
+ lastOutput->connect(mManyToMultiConverter->inputs[i].get());
+ }
+ lastOutput = &mManyToMultiConverter->output;
+ setAudioBalance(audioBalance);
+ }
+
switch (sinkFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
mSink = std::make_unique<SinkFloat>(sinkChannelCount);
@@ -125,9 +146,32 @@
* @param volume between 0.0 and 1.0
*/
void AAudioFlowGraph::setTargetVolume(float volume) {
- mVolumeRamp->setTarget(volume);
+ for (int i = 0; i < mVolumeRamps.size(); i++) {
+ mVolumeRamps[i]->setTarget(volume * mPanningVolumes[i]);
+ }
+ mTargetVolume = volume;
}
+/**
+ * @param audioBalance between -1.0 and 1.0
+ */
+void AAudioFlowGraph::setAudioBalance(float audioBalance) {
+ if (mPanningVolumes.size() >= 2) {
+ float leftMultiplier = 0;
+ float rightMultiplier = 0;
+ mBalance.computeStereoBalance(audioBalance, &leftMultiplier, &rightMultiplier);
+ mPanningVolumes[0] = leftMultiplier;
+ mPanningVolumes[1] = rightMultiplier;
+ mVolumeRamps[0]->setTarget(mTargetVolume * leftMultiplier);
+ mVolumeRamps[1]->setTarget(mTargetVolume * rightMultiplier);
+ }
+}
+
+/**
+ * @param numFrames to slowly adjust for volume changes
+ */
void AAudioFlowGraph::setRampLengthInFrames(int32_t numFrames) {
- mVolumeRamp->setLengthInFrames(numFrames);
+ for (auto& ramp : mVolumeRamps) {
+ ramp->setLengthInFrames(numFrames);
+ }
}
diff --git a/media/libaaudio/src/client/AAudioFlowGraph.h b/media/libaaudio/src/client/AAudioFlowGraph.h
index e719d91..00b6575 100644
--- a/media/libaaudio/src/client/AAudioFlowGraph.h
+++ b/media/libaaudio/src/client/AAudioFlowGraph.h
@@ -23,9 +23,12 @@
#include <system/audio.h>
#include <aaudio/AAudio.h>
+#include <audio_utils/Balance.h>
#include <flowgraph/ClipToRange.h>
+#include <flowgraph/ManyToMultiConverter.h>
#include <flowgraph/MonoBlend.h>
#include <flowgraph/MonoToMultiConverter.h>
+#include <flowgraph/MultiToManyConverter.h>
#include <flowgraph/RampLinear.h>
class AAudioFlowGraph {
@@ -37,13 +40,19 @@
* @param sourceChannelCount
* @param sinkFormat
* @param sinkChannelCount
+ * @param useMonoBlend
+ * @param audioBalance
+ * @param channelMask
+ * @param isExclusive
* @return
*/
aaudio_result_t configure(audio_format_t sourceFormat,
int32_t sourceChannelCount,
audio_format_t sinkFormat,
int32_t sinkChannelCount,
- bool useMonoBlend);
+ bool useMonoBlend,
+ float audioBalance,
+ bool isExclusive);
void process(const void *source, void *destination, int32_t numFrames);
@@ -52,14 +61,27 @@
*/
void setTargetVolume(float volume);
+ /**
+ * @param audioBalance between -1.0 and 1.0
+ */
+ void setAudioBalance(float audioBalance);
+
+ /**
+ * @param numFrames to slowly adjust for volume changes
+ */
void setRampLengthInFrames(int32_t numFrames);
private:
std::unique_ptr<flowgraph::FlowGraphSourceBuffered> mSource;
std::unique_ptr<flowgraph::MonoBlend> mMonoBlend;
- std::unique_ptr<flowgraph::RampLinear> mVolumeRamp;
std::unique_ptr<flowgraph::ClipToRange> mClipper;
std::unique_ptr<flowgraph::MonoToMultiConverter> mChannelConverter;
+ std::unique_ptr<flowgraph::ManyToMultiConverter> mManyToMultiConverter;
+ std::unique_ptr<flowgraph::MultiToManyConverter> mMultiToManyConverter;
+ std::vector<std::unique_ptr<flowgraph::RampLinear>> mVolumeRamps;
+ std::vector<float> mPanningVolumes;
+ float mTargetVolume = 1.0f;
+ android::audio_utils::Balance mBalance;
std::unique_ptr<flowgraph::FlowGraphSink> mSink;
};
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 1b8e224..afdc2ac 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -272,12 +272,15 @@
}
// Exclusive output streams should combine channels when mono audio adjustment
- // is enabled.
+ // is enabled. They should also adjust for audio balance.
if ((getDirection() == AAUDIO_DIRECTION_OUTPUT) &&
(getSharingMode() == AAUDIO_SHARING_MODE_EXCLUSIVE)) {
bool isMasterMono = false;
android::AudioSystem::getMasterMono(&isMasterMono);
setRequireMonoBlend(isMasterMono);
+ float audioBalance = 0;
+ android::AudioSystem::getMasterBalance(&audioBalance);
+ setAudioBalance(audioBalance);
}
// For debugging and analyzing the distribution of MMAP timestamps.
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index c17c7a0..450d390 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -53,7 +53,9 @@
getSamplesPerFrame(),
getDeviceFormat(),
getDeviceChannelCount(),
- getRequireMonoBlend());
+ getRequireMonoBlend(),
+ getAudioBalance(),
+ (getSharingMode() == AAUDIO_SHARING_MODE_EXCLUSIVE));
if (result != AAUDIO_OK) {
safeReleaseClose();
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index a3af753..5fb4528 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -281,6 +281,10 @@
return mRequireMonoBlend;
}
+ float getAudioBalance() const {
+ return mAudioBalance;
+ }
+
/**
* This is only valid after setChannelMask() and setFormat()
* have been called.
@@ -642,6 +646,13 @@
mRequireMonoBlend = requireMonoBlend;
}
+ /**
+ * This should not be called after the open() call.
+ */
+ void setAudioBalance(float audioBalance) {
+ mAudioBalance = audioBalance;
+ }
+
std::string mMetricsId; // set once during open()
std::mutex mStreamLock;
@@ -684,6 +695,7 @@
aaudio_allowed_capture_policy_t mAllowedCapturePolicy = AAUDIO_ALLOW_CAPTURE_BY_ALL;
bool mIsPrivacySensitive = false;
bool mRequireMonoBlend = false;
+ float mAudioBalance = 0;
int32_t mSessionId = AAUDIO_UNSPECIFIED;
diff --git a/media/libaaudio/src/flowgraph/MultiToManyConverter.cpp b/media/libaaudio/src/flowgraph/MultiToManyConverter.cpp
new file mode 100644
index 0000000..f074364
--- /dev/null
+++ b/media/libaaudio/src/flowgraph/MultiToManyConverter.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 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 <unistd.h>
+#include "FlowGraphNode.h"
+#include "MultiToManyConverter.h"
+
+using namespace flowgraph;
+
+MultiToManyConverter::MultiToManyConverter(int32_t channelCount)
+ : outputs(channelCount)
+ , input(*this, channelCount) {
+ for (int i = 0; i < channelCount; i++) {
+ outputs[i] = std::make_unique<FlowGraphPortFloatOutput>(*this, 1);
+ }
+}
+
+MultiToManyConverter::~MultiToManyConverter() = default;
+
+int32_t MultiToManyConverter::onProcess(int32_t numFrames) {
+ int32_t channelCount = input.getSamplesPerFrame();
+
+ for (int ch = 0; ch < channelCount; ch++) {
+ const float *inputBuffer = input.getBuffer() + ch;
+ float *outputBuffer = outputs[ch]->getBuffer();
+
+ for (int i = 0; i < numFrames; i++) {
+ *outputBuffer++ = *inputBuffer;
+ inputBuffer += channelCount;
+ }
+ }
+
+ return numFrames;
+}
+
diff --git a/media/libaaudio/src/flowgraph/MultiToManyConverter.h b/media/libaaudio/src/flowgraph/MultiToManyConverter.h
new file mode 100644
index 0000000..de31475
--- /dev/null
+++ b/media/libaaudio/src/flowgraph/MultiToManyConverter.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 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.
+ */
+
+#ifndef FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H
+#define FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "FlowGraphNode.h"
+
+namespace flowgraph {
+
+/**
+ * Convert a multi-channel interleaved stream to multiple mono-channel
+ * outputs
+ */
+ class MultiToManyConverter : public FlowGraphNode {
+ public:
+ explicit MultiToManyConverter(int32_t channelCount);
+
+ virtual ~MultiToManyConverter();
+
+ int32_t onProcess(int32_t numFrames) override;
+
+ const char *getName() override {
+ return "MultiToManyConverter";
+ }
+
+ std::vector<std::unique_ptr<flowgraph::FlowGraphPortFloatOutput>> outputs;
+ flowgraph::FlowGraphPortFloatInput input;
+ };
+
+} /* namespace flowgraph */
+
+#endif //FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H
diff --git a/media/libaudioclient/Android.bp b/media/libaudioclient/Android.bp
index 7e180a2..ab75c97 100644
--- a/media/libaudioclient/Android.bp
+++ b/media/libaudioclient/Android.bp
@@ -205,7 +205,7 @@
],
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth.updatable",
+ "com.android.bluetooth",
"com.android.media",
"com.android.media.swcodec",
],
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp
index ea4faa8..f191c49 100644
--- a/media/libaudioclient/AudioRecord.cpp
+++ b/media/libaudioclient/AudioRecord.cpp
@@ -1077,6 +1077,7 @@
.set(AMEDIAMETRICS_PROP_SAMPLERATE, (int32_t)mSampleRate)
// the following are NOT immutable
.set(AMEDIAMETRICS_PROP_STATE, stateToString(mActive))
+ .set(AMEDIAMETRICS_PROP_STATUS, (int32_t)status)
.set(AMEDIAMETRICS_PROP_SELECTEDMICDIRECTION, (int32_t)mSelectedMicDirection)
.set(AMEDIAMETRICS_PROP_SELECTEDMICFIELDDIRECTION, (double)mSelectedMicFieldDimension)
.record();
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 066a7ae..8be62ed 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -2121,6 +2121,7 @@
.set(AMEDIAMETRICS_PROP_VOLUME_LEFT, (double)mVolume[AUDIO_INTERLEAVE_LEFT])
.set(AMEDIAMETRICS_PROP_VOLUME_RIGHT, (double)mVolume[AUDIO_INTERLEAVE_RIGHT])
.set(AMEDIAMETRICS_PROP_STATE, stateToString(mState))
+ .set(AMEDIAMETRICS_PROP_STATUS, (int32_t)NO_ERROR)
.set(AMEDIAMETRICS_PROP_AUXEFFECTID, (int32_t)mAuxEffectId)
.set(AMEDIAMETRICS_PROP_SAMPLERATE, (int32_t)mSampleRate)
.set(AMEDIAMETRICS_PROP_PLAYBACK_SPEED, (double)mPlaybackRate.mSpeed)
@@ -2163,8 +2164,8 @@
// Ensure these variables are initialized in set().
mediametrics::LogItem(AMEDIAMETRICS_KEY_AUDIO_TRACK_ERROR)
.set(AMEDIAMETRICS_PROP_EVENT, event)
- .set(AMEDIAMETRICS_PROP_ERROR, mediametrics::statusToErrorString(status))
- .set(AMEDIAMETRICS_PROP_ERRORMESSAGE, message)
+ .set(AMEDIAMETRICS_PROP_STATUS, (int32_t)status)
+ .set(AMEDIAMETRICS_PROP_STATUSMESSAGE, message)
.set(AMEDIAMETRICS_PROP_ORIGINALFLAGS, toString(mOrigFlags).c_str())
.set(AMEDIAMETRICS_PROP_SESSIONID, (int32_t)mSessionId)
.set(AMEDIAMETRICS_PROP_CONTENTTYPE, toString(mAttributes.content_type).c_str())
diff --git a/media/libaudioclient/AudioTrackShared.cpp b/media/libaudioclient/AudioTrackShared.cpp
index da27dc8..e3b79b2 100644
--- a/media/libaudioclient/AudioTrackShared.cpp
+++ b/media/libaudioclient/AudioTrackShared.cpp
@@ -490,6 +490,8 @@
status_t AudioTrackClientProxy::waitStreamEndDone(const struct timespec *requested)
{
struct timespec total; // total elapsed time spent waiting
+ struct timespec before;
+ bool beforeIsValid = false;
total.tv_sec = 0;
total.tv_nsec = 0;
audio_track_cblk_t* cblk = mCblk;
@@ -570,17 +572,38 @@
}
int32_t old = android_atomic_and(~CBLK_FUTEX_WAKE, &cblk->mFutex);
if (!(old & CBLK_FUTEX_WAKE)) {
+ if (!beforeIsValid) {
+ clock_gettime(CLOCK_MONOTONIC, &before);
+ beforeIsValid = true;
+ }
errno = 0;
(void) syscall(__NR_futex, &cblk->mFutex,
mClientInServer ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT, old & ~CBLK_FUTEX_WAKE, ts);
- switch (errno) {
+ status_t error = errno; // clock_gettime can affect errno
+ {
+ struct timespec after;
+ clock_gettime(CLOCK_MONOTONIC, &after);
+ total.tv_sec += after.tv_sec - before.tv_sec;
+ // Use auto instead of long to avoid the google-runtime-int warning.
+ auto deltaNs = after.tv_nsec - before.tv_nsec;
+ if (deltaNs < 0) {
+ deltaNs += 1000000000;
+ total.tv_sec--;
+ }
+ if ((total.tv_nsec += deltaNs) >= 1000000000) {
+ total.tv_nsec -= 1000000000;
+ total.tv_sec++;
+ }
+ before = after;
+ }
+ switch (error) {
case 0: // normal wakeup by server, or by binderDied()
case EWOULDBLOCK: // benign race condition with server
case EINTR: // wait was interrupted by signal or other spurious wakeup
case ETIMEDOUT: // time-out expired
break;
default:
- status = errno;
+ status = error;
ALOGE("%s unexpected error %s", __func__, strerror(status));
goto end;
}
diff --git a/media/libaudiofoundation/AudioContainers.cpp b/media/libaudiofoundation/AudioContainers.cpp
index 117d188..553a319 100644
--- a/media/libaudiofoundation/AudioContainers.cpp
+++ b/media/libaudiofoundation/AudioContainers.cpp
@@ -89,6 +89,11 @@
return ss.str();
}
+bool deviceTypesToString(const DeviceTypeSet &deviceTypes, std::string &str) {
+ str = deviceTypesToString(deviceTypes);
+ return true;
+}
+
std::string dumpDeviceTypes(const DeviceTypeSet &deviceTypes) {
std::stringstream ss;
for (auto it = deviceTypes.begin(); it != deviceTypes.end(); ++it) {
diff --git a/media/libaudiofoundation/include/media/AudioContainers.h b/media/libaudiofoundation/include/media/AudioContainers.h
index 707ab68..d352a96 100644
--- a/media/libaudiofoundation/include/media/AudioContainers.h
+++ b/media/libaudiofoundation/include/media/AudioContainers.h
@@ -133,6 +133,8 @@
std::string deviceTypesToString(const DeviceTypeSet& deviceTypes);
+bool deviceTypesToString(const DeviceTypeSet& deviceTypes, std::string &str);
+
std::string dumpDeviceTypes(const DeviceTypeSet& deviceTypes);
/**
diff --git a/media/libmediahelper/Android.bp b/media/libmediahelper/Android.bp
index 9b54199..a433fc6 100644
--- a/media/libmediahelper/Android.bp
+++ b/media/libmediahelper/Android.bp
@@ -20,7 +20,7 @@
},
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth.updatable",
+ "com.android.bluetooth",
"com.android.media",
"com.android.media.swcodec",
],
diff --git a/media/libmediametrics/MediaMetricsItem.cpp b/media/libmediametrics/MediaMetricsItem.cpp
index a7ec975..57fc49d 100644
--- a/media/libmediametrics/MediaMetricsItem.cpp
+++ b/media/libmediametrics/MediaMetricsItem.cpp
@@ -57,18 +57,19 @@
// This may be found in frameworks/av/media/libmediametrics/include/MediaMetricsConstants.h
static std::unordered_map<std::string, int32_t> map{
{"", NO_ERROR},
- {AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT, BAD_VALUE},
- {AMEDIAMETRICS_PROP_ERROR_VALUE_IO, DEAD_OBJECT},
- {AMEDIAMETRICS_PROP_ERROR_VALUE_MEMORY, NO_MEMORY},
- {AMEDIAMETRICS_PROP_ERROR_VALUE_SECURITY, PERMISSION_DENIED},
- {AMEDIAMETRICS_PROP_ERROR_VALUE_STATE, INVALID_OPERATION},
- {AMEDIAMETRICS_PROP_ERROR_VALUE_TIMEOUT, WOULD_BLOCK},
- {AMEDIAMETRICS_PROP_ERROR_VALUE_UNKNOWN, UNKNOWN_ERROR},
+ {AMEDIAMETRICS_PROP_STATUS_VALUE_OK, NO_ERROR},
+ {AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT, BAD_VALUE},
+ {AMEDIAMETRICS_PROP_STATUS_VALUE_IO, DEAD_OBJECT},
+ {AMEDIAMETRICS_PROP_STATUS_VALUE_MEMORY, NO_MEMORY},
+ {AMEDIAMETRICS_PROP_STATUS_VALUE_SECURITY, PERMISSION_DENIED},
+ {AMEDIAMETRICS_PROP_STATUS_VALUE_STATE, INVALID_OPERATION},
+ {AMEDIAMETRICS_PROP_STATUS_VALUE_TIMEOUT, WOULD_BLOCK},
+ {AMEDIAMETRICS_PROP_STATUS_VALUE_UNKNOWN, UNKNOWN_ERROR},
};
return map;
}
-status_t errorStringToStatus(const char *error) {
+status_t statusStringToStatus(const char *error) {
const auto& map = getErrorStringMap();
if (error == nullptr || error[0] == '\0') return NO_ERROR;
auto it = map.find(error);
diff --git a/media/libmediametrics/include/MediaMetricsConstants.h b/media/libmediametrics/include/MediaMetricsConstants.h
index aeaa49c..2bf72a7 100644
--- a/media/libmediametrics/include/MediaMetricsConstants.h
+++ b/media/libmediametrics/include/MediaMetricsConstants.h
@@ -119,18 +119,6 @@
#define AMEDIAMETRICS_PROP_DURATIONNS "durationNs" // int64 duration time span
#define AMEDIAMETRICS_PROP_ENCODING "encoding" // string value of format
-// Error statistics
-#define AMEDIAMETRICS_PROP_ERROR "error#" // string, empty or one of
- // AMEDIAMETRICS_PROP_ERROR_VALUE_*
- // Used for error categorization.
-#define AMEDIAMETRICS_PROP_ERRORSUBCODE "errorSubCode#" // int32, specific code for error
- // used in conjunction with error#.
-#define AMEDIAMETRICS_PROP_ERRORMESSAGE "errorMessage#" // string, supplemental to error.
- // Arbitrary information treated as
- // informational, may be logcat msg,
- // or an exception with stack trace.
- // Treated as "debug" information.
-
#define AMEDIAMETRICS_PROP_EVENT "event#" // string value (often func name)
#define AMEDIAMETRICS_PROP_EXECUTIONTIMENS "executionTimeNs" // time to execute the event
@@ -162,7 +150,17 @@
#define AMEDIAMETRICS_PROP_STARTUPMS "startupMs" // double value
// State is "ACTIVE" or "STOPPED" for AudioRecord
#define AMEDIAMETRICS_PROP_STATE "state" // string
-#define AMEDIAMETRICS_PROP_STATUS "status" // int32 status_t
+#define AMEDIAMETRICS_PROP_STATUS "status#" // int32 status_t
+ // AAudio uses their own status codes
+// Supplemental information to the status code.
+#define AMEDIAMETRICS_PROP_STATUSSUBCODE "statusSubCode" // int32, specific code
+ // used in conjunction with status.
+#define AMEDIAMETRICS_PROP_STATUSMESSAGE "statusMessage" // string, supplemental info.
+ // Arbitrary information treated as
+ // informational, may be logcat msg,
+ // or an exception with stack trace.
+ // Treated as "debug" information.
+
#define AMEDIAMETRICS_PROP_STREAMTYPE "streamType" // string (AudioTrack)
#define AMEDIAMETRICS_PROP_THREADID "threadId" // int32 value io handle
#define AMEDIAMETRICS_PROP_THROTTLEMS "throttleMs" // double
@@ -237,16 +235,20 @@
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/include/binder/Status.h;drc=88e25c0861499ee3ab885814dddc097ab234cb7b;l=57
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/AudioSystem.java;drc=3ac246c43294d7f7012bdcb0ccb7bae1aa695bd4;l=785
// https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libaaudio/include/aaudio/AAudio.h;drc=cfd3a6fa3aaaf712a890dc02452b38ef401083b8;l=120
+// https://abseil.io/docs/cpp/guides/status-codes
-// Error category:
-// An empty error string indicates no error.
+// Status errors:
+// An empty status string or "ok" is interpreted as no error.
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_OK "ok"
// Error category: argument
// IllegalArgumentException
// NullPointerException
// BAD_VALUE
+// absl::INVALID_ARGUMENT
+// absl::OUT_OF_RANGE
// Out of range, out of bounds.
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT "argument"
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT "argument"
// Error category: io
// IOException
@@ -257,36 +259,48 @@
// file or ioctl failure
// Service, rpc, binder, or socket failure.
// Hardware or device failure.
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_IO "io"
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_IO "io"
// Error category: outOfMemory
// OutOfMemoryException
// NO_MEMORY
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_MEMORY "memory"
+// absl::RESOURCE_EXHAUSTED
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_MEMORY "memory"
// Error category: security
// SecurityException
// PERMISSION_DENIED
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_SECURITY "security"
+// absl::PERMISSION_DENIED
+// absl::UNAUTHENTICATED
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_SECURITY "security"
// Error category: state
// IllegalStateException
// UnsupportedOperationException
// INVALID_OPERATION
// NO_INIT
+// absl::NOT_FOUND
+// absl::ALREADY_EXISTS
+// absl::FAILED_PRECONDITION
+// absl::UNAVAILABLE
+// absl::UNIMPLEMENTED
// Functionality not implemented (argument may or may not be correct).
// Call unexpected or out of order.
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_STATE "state"
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_STATE "state"
// Error category: timeout
// TimeoutException
// WOULD_BLOCK
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_TIMEOUT "timeout"
+// absl::DEADLINE_EXCEEDED
+// absl::ABORTED
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_TIMEOUT "timeout"
// Error category: unknown
// Exception (Java specified not listed above, or custom app/service)
// UNKNOWN_ERROR
+// absl::INTERNAL
+// absl::DATA_LOSS
// Catch-all bucket for errors not listed above.
-#define AMEDIAMETRICS_PROP_ERROR_VALUE_UNKNOWN "unknown"
+#define AMEDIAMETRICS_PROP_STATUS_VALUE_UNKNOWN "unknown"
#endif // ANDROID_MEDIA_MEDIAMETRICSCONSTANTS_H
diff --git a/media/libmediametrics/include/media/MediaMetricsItem.h b/media/libmediametrics/include/media/MediaMetricsItem.h
index 87f608f..de56665 100644
--- a/media/libmediametrics/include/media/MediaMetricsItem.h
+++ b/media/libmediametrics/include/media/MediaMetricsItem.h
@@ -106,34 +106,34 @@
};
/*
- * Helper for error conversions
+ * Helper for status conversions
*/
-static inline constexpr const char* statusToErrorString(status_t status) {
+inline constexpr const char* statusToStatusString(status_t status) {
switch (status) {
- case NO_ERROR:
- return "";
case BAD_VALUE:
- return AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT;
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT;
case DEAD_OBJECT:
case FAILED_TRANSACTION:
- return AMEDIAMETRICS_PROP_ERROR_VALUE_IO;
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_IO;
case NO_MEMORY:
- return AMEDIAMETRICS_PROP_ERROR_VALUE_MEMORY;
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_MEMORY;
case PERMISSION_DENIED:
- return AMEDIAMETRICS_PROP_ERROR_VALUE_SECURITY;
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_SECURITY;
case NO_INIT:
case INVALID_OPERATION:
- return AMEDIAMETRICS_PROP_ERROR_VALUE_STATE;
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_STATE;
case WOULD_BLOCK:
- return AMEDIAMETRICS_PROP_ERROR_VALUE_TIMEOUT;
- case UNKNOWN_ERROR:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_TIMEOUT;
default:
- return AMEDIAMETRICS_PROP_ERROR_VALUE_UNKNOWN;
+ if (status >= 0) return AMEDIAMETRICS_PROP_STATUS_VALUE_OK; // non-negative values "OK"
+ [[fallthrough]]; // negative values are error.
+ case UNKNOWN_ERROR:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_UNKNOWN;
}
}
-status_t errorStringToStatus(const char *error);
+status_t statusStringToStatus(const char *error);
/*
* Time printing
diff --git a/media/libstagefright/TEST_MAPPING b/media/libstagefright/TEST_MAPPING
index 0987a5b..7d4e168 100644
--- a/media/libstagefright/TEST_MAPPING
+++ b/media/libstagefright/TEST_MAPPING
@@ -40,6 +40,17 @@
"exclude-filter": "android.media.audio.cts.AudioRecordTest"
}
]
+ },
+ {
+ "name": "CtsMediaPlayerTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
+ }
+ ]
}
],
"presubmit": [
diff --git a/media/libstagefright/codecs/m4v_h263/enc/SoftMPEG4Encoder.cpp b/media/libstagefright/codecs/m4v_h263/enc/SoftMPEG4Encoder.cpp
index fb6c4e2..bb1cb0b 100644
--- a/media/libstagefright/codecs/m4v_h263/enc/SoftMPEG4Encoder.cpp
+++ b/media/libstagefright/codecs/m4v_h263/enc/SoftMPEG4Encoder.cpp
@@ -354,7 +354,7 @@
}
if (mpeg4type->eProfile != OMX_VIDEO_MPEG4ProfileCore ||
- mpeg4type->eLevel != OMX_VIDEO_MPEG4Level2 ||
+ mpeg4type->eLevel > OMX_VIDEO_MPEG4Level2 ||
(mpeg4type->nAllowedPictureTypes & OMX_VIDEO_PictureTypeB) ||
mpeg4type->nBFrames != 0 ||
mpeg4type->nIDCVLCThreshold != 0 ||
diff --git a/media/libstagefright/foundation/AMessage.cpp b/media/libstagefright/foundation/AMessage.cpp
index c2114b3..5c99cc9 100644
--- a/media/libstagefright/foundation/AMessage.cpp
+++ b/media/libstagefright/foundation/AMessage.cpp
@@ -33,7 +33,7 @@
#include <media/stagefright/foundation/hexdump.h>
-#if !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#if defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
#include <binder/Parcel.h>
#endif
@@ -659,7 +659,7 @@
return s;
}
-#if !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#if defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
// static
sp<AMessage> AMessage::FromParcel(const Parcel &parcel, size_t maxNestingLevel) {
int32_t what = parcel.readInt32();
@@ -825,7 +825,7 @@
}
}
}
-#endif // !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#endif // defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
sp<AMessage> AMessage::changesFrom(const sp<const AMessage> &other, bool deep) const {
if (other == NULL) {
diff --git a/media/libstagefright/foundation/AString.cpp b/media/libstagefright/foundation/AString.cpp
index b1ed077..a5e0ff8 100644
--- a/media/libstagefright/foundation/AString.cpp
+++ b/media/libstagefright/foundation/AString.cpp
@@ -27,7 +27,7 @@
#include "ADebug.h"
#include "AString.h"
-#if !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#if defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
#include <binder/Parcel.h>
#endif
@@ -365,7 +365,7 @@
return !strcasecmp(mData + mSize - suffixLen, suffix);
}
-#if !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#if defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
// static
AString AString::FromParcel(const Parcel &parcel) {
size_t size = static_cast<size_t>(parcel.readInt32());
@@ -380,7 +380,7 @@
}
return err;
}
-#endif // !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#endif // defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
AString AStringPrintf(const char *format, ...) {
va_list ap;
diff --git a/media/libstagefright/foundation/MetaData.cpp b/media/libstagefright/foundation/MetaData.cpp
index 7f48cfd..77913d5 100644
--- a/media/libstagefright/foundation/MetaData.cpp
+++ b/media/libstagefright/foundation/MetaData.cpp
@@ -28,7 +28,7 @@
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/MetaData.h>
-#if !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#if defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
#include <binder/Parcel.h>
#endif
@@ -48,7 +48,7 @@
MetaData::~MetaData() {
}
-#if !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#if defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
/* static */
sp<MetaData> MetaData::createFromParcel(const Parcel &parcel) {
diff --git a/media/libstagefright/foundation/MetaDataBase.cpp b/media/libstagefright/foundation/MetaDataBase.cpp
index 3f050ea..980eb22 100644
--- a/media/libstagefright/foundation/MetaDataBase.cpp
+++ b/media/libstagefright/foundation/MetaDataBase.cpp
@@ -28,7 +28,7 @@
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/MetaDataBase.h>
-#if !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#if defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
#include <binder/Parcel.h>
#endif
@@ -452,7 +452,7 @@
}
}
-#if !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#if defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
status_t MetaDataBase::writeToParcel(Parcel &parcel) {
status_t ret;
size_t numItems = mInternalData->mItems.size();
@@ -532,7 +532,7 @@
ALOGW("no metadata in parcel");
return UNKNOWN_ERROR;
}
-#endif // !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
+#endif // defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
} // namespace android
diff --git a/media/ndk/Android.bp b/media/ndk/Android.bp
index 8d527e9..6c5e6cb 100644
--- a/media/ndk/Android.bp
+++ b/media/ndk/Android.bp
@@ -167,7 +167,7 @@
stubs: {
symbol_file: "libmediandk.map.txt",
versions: ["29"],
- },
+ }
}
cc_library {
diff --git a/media/ndk/NdkMediaDrm.cpp b/media/ndk/NdkMediaDrm.cpp
index 6e9945d..59c1103 100644
--- a/media/ndk/NdkMediaDrm.cpp
+++ b/media/ndk/NdkMediaDrm.cpp
@@ -97,6 +97,8 @@
List<idvec_t> mIds;
KeyedVector<String8, String8> mQueryResults;
Vector<uint8_t> mKeyRequest;
+ String8 mDefaultUrl;
+ AMediaDrmKeyRequestType mkeyRequestType;
Vector<uint8_t> mProvisionRequest;
String8 mProvisionUrl;
String8 mPropertyString;
@@ -416,6 +418,21 @@
const AMediaDrmKeyValue *optionalParameters, size_t numOptionalParameters,
const uint8_t **keyRequest, size_t *keyRequestSize) {
+ return AMediaDrm_getKeyRequestWithDefaultUrlAndType(mObj,
+ scope, init, initSize, mimeType, keyType, optionalParameters,
+ numOptionalParameters, keyRequest,
+ keyRequestSize, NULL, NULL);
+}
+
+EXPORT
+media_status_t AMediaDrm_getKeyRequestWithDefaultUrlAndType(AMediaDrm *mObj,
+ const AMediaDrmScope *scope, const uint8_t *init, size_t initSize,
+ const char *mimeType, AMediaDrmKeyType keyType,
+ const AMediaDrmKeyValue *optionalParameters,
+ size_t numOptionalParameters, const uint8_t **keyRequest,
+ size_t *keyRequestSize, const char **defaultUrl,
+ AMediaDrmKeyRequestType *keyRequestType) {
+
if (!mObj || mObj->mDrm == NULL) {
return AMEDIA_ERROR_INVALID_OBJECT;
}
@@ -449,18 +466,43 @@
mdOptionalParameters.add(String8(optionalParameters[i].mKey),
String8(optionalParameters[i].mValue));
}
- String8 defaultUrl;
- DrmPlugin::KeyRequestType keyRequestType;
+
+ DrmPlugin::KeyRequestType requestType;
mObj->mKeyRequest.clear();
status_t status = mObj->mDrm->getKeyRequest(*iter, mdInit, String8(mimeType),
- mdKeyType, mdOptionalParameters, mObj->mKeyRequest, defaultUrl,
- &keyRequestType);
+ mdKeyType, mdOptionalParameters, mObj->mKeyRequest, mObj->mDefaultUrl,
+ &requestType);
if (status != OK) {
return translateStatus(status);
} else {
*keyRequest = mObj->mKeyRequest.array();
*keyRequestSize = mObj->mKeyRequest.size();
+ if (defaultUrl != NULL)
+ *defaultUrl = mObj->mDefaultUrl.string();
+ switch(requestType) {
+ case DrmPlugin::kKeyRequestType_Initial:
+ mObj->mkeyRequestType = KEY_REQUEST_TYPE_INITIAL;
+ break;
+ case DrmPlugin::kKeyRequestType_Renewal:
+ mObj->mkeyRequestType = KEY_REQUEST_TYPE_RENEWAL;
+ break;
+ case DrmPlugin::kKeyRequestType_Release:
+ mObj->mkeyRequestType = KEY_REQUEST_TYPE_RELEASE;
+ break;
+ case DrmPlugin::kKeyRequestType_None:
+ mObj->mkeyRequestType = KEY_REQUEST_TYPE_NONE;
+ break;
+ case DrmPlugin::kKeyRequestType_Update:
+ mObj->mkeyRequestType = KEY_REQUEST_TYPE_UPDATE;
+ break;
+ default:
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ if (keyRequestType != NULL)
+ *keyRequestType = mObj->mkeyRequestType;
}
+
return AMEDIA_OK;
}
diff --git a/media/ndk/include/media/NdkMediaDrm.h b/media/ndk/include/media/NdkMediaDrm.h
index 849a8f9..4eca3d7 100644
--- a/media/ndk/include/media/NdkMediaDrm.h
+++ b/media/ndk/include/media/NdkMediaDrm.h
@@ -112,6 +112,41 @@
} AMediaDrmKeyType;
/**
+ * Introduced in API 33.
+ */
+typedef enum AMediaDrmKeyRequestType : int32_t {
+ /**
+ * Key request type is initial license request.
+ * An initial license request is necessary to load keys.
+ */
+ KEY_REQUEST_TYPE_INITIAL,
+
+ /**
+ * Key request type is license renewal.
+ * A renewal license request is necessary to prevent the keys from expiring.
+ */
+ KEY_REQUEST_TYPE_RENEWAL,
+
+ /**
+ * Key request type is license release.
+ * A license release request indicates that keys are removed.
+ */
+ KEY_REQUEST_TYPE_RELEASE,
+
+ /**
+ * Keys are already loaded and are available for use. No license request is necessary, and
+ * no key request data is returned.
+ */
+ KEY_REQUEST_TYPE_NONE,
+
+ /**
+ * Keys have been loaded but an additional license request is needed
+ * to update their values.
+ */
+ KEY_REQUEST_TYPE_UPDATE
+} AMediaDrmKeyRequestType;
+
+/**
* Data type containing {key, value} pair
*/
typedef struct AMediaDrmKeyValuePair {
@@ -248,7 +283,10 @@
* to obtain or release keys used to decrypt encrypted content.
* AMediaDrm_getKeyRequest is used to obtain an opaque key request byte array that
* is delivered to the license server. The opaque key request byte array is
- * returned in KeyRequest.data.
+ * returned in *keyRequest and the number of bytes in the request is
+ * returned in *keyRequestSize.
+ * This API has same functionality as AMediaDrm_getKeyRequestWithDefaultUrlAndType()
+ * when defaultUrl and keyRequestType are passed in as NULL.
*
* After the app has received the key request response from the server,
* it should deliver to the response to the DRM engine plugin using the method
@@ -280,11 +318,14 @@
* by the caller
*
* On exit:
+ * If this returns AMEDIA_OK,
* 1. The keyRequest pointer will reference the opaque key request data. It
* will reside in memory owned by the AMediaDrm object, and will remain
- * accessible until the next call to AMediaDrm_getKeyRequest or until the
+ * accessible until the next call to AMediaDrm_getKeyRequest
+ * or AMediaDrm_getKeyRequestWithDefaultUrlAndType or until the
* MediaDrm object is released.
* 2. keyRequestSize will be set to the size of the request
+ * If this does not return AMEDIA_OK, value of these parameters should not be used.
*
* Returns MEDIADRM_NOT_PROVISIONED_ERROR if reprovisioning is needed, due to a
* problem with the device certificate.
@@ -297,6 +338,72 @@
const uint8_t **keyRequest, size_t *keyRequestSize) __INTRODUCED_IN(21);
/**
+ * A key request/response exchange occurs between the app and a license server
+ * to obtain or release keys used to decrypt encrypted content.
+ * AMediaDrm_getKeyRequest is used to obtain an opaque key request byte array that
+ * is delivered to the license server. The opaque key request byte array is
+ * returned in *keyRequest and the number of bytes in the request is
+ * returned in *keyRequestSize.
+ *
+ * After the app has received the key request response from the server,
+ * it should deliver to the response to the DRM engine plugin using the method
+ * AMediaDrm_provideKeyResponse.
+ *
+ * scope may be a sessionId or a keySetId, depending on the specified keyType.
+ * When the keyType is KEY_TYPE_STREAMING or KEY_TYPE_OFFLINE, scope should be set
+ * to the sessionId the keys will be provided to. When the keyType is
+ * KEY_TYPE_RELEASE, scope should be set to the keySetId of the keys being released.
+ * Releasing keys from a device invalidates them for all sessions.
+ *
+ * init container-specific data, its meaning is interpreted based on the mime type
+ * provided in the mimeType parameter. It could contain, for example, the content
+ * ID, key ID or other data obtained from the content metadata that is required in
+ * generating the key request. init may be null when keyType is KEY_TYPE_RELEASE.
+ *
+ * initSize is the number of bytes of initData
+ *
+ * mimeType identifies the mime type of the content.
+ *
+ * keyType specifes the type of the request. The request may be to acquire keys for
+ * streaming or offline content, or to release previously acquired keys, which are
+ * identified by a keySetId.
+ *
+ * optionalParameters are included in the key request message to allow a client
+ * application to provide additional message parameters to the server.
+ *
+ * numOptionalParameters indicates the number of optional parameters provided
+ * by the caller
+ *
+ * On exit:
+ * If this returns AMEDIA_OK,
+ * 1. The keyRequest pointer will reference the opaque key request data. It
+ * will reside in memory owned by the AMediaDrm object, and will remain
+ * accessible until the next call to either AMediaDrm_getKeyRequest
+ * or AMediaDrm_getKeyRequestWithDefaultUrlAndType or until the
+ * MediaDrm object is released.
+ * 2. keyRequestSize will be set to the size of the request.
+ * 3. defaultUrl will be set to the recommended URL to deliver the key request.
+ * The defaultUrl pointer will reference a NULL terminated URL string.
+ * It will be UTF-8 encoded and have same lifetime with the key request data
+ * KeyRequest pointer references to. Passing in NULL means you don't need it
+ * to be reported.
+ * 4. keyRequestType will be set to the key request type. Passing in NULL means
+* you don't need it to be reported.
+ *
+ * Returns MEDIADRM_NOT_PROVISIONED_ERROR if reprovisioning is needed, due to a
+ * problem with the device certificate.
+ *
+ * Available since API level 33.
+ */
+media_status_t AMediaDrm_getKeyRequestWithDefaultUrlAndType(AMediaDrm *,
+ const AMediaDrmScope *scope, const uint8_t *init, size_t initSize,
+ const char *mimeType, AMediaDrmKeyType keyType,
+ const AMediaDrmKeyValue *optionalParameters,
+ size_t numOptionalParameters, const uint8_t **keyRequest,
+ size_t *keyRequestSize, const char **defaultUrl,
+ AMediaDrmKeyRequestType *keyRequestType) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
* A key response is received from the license server by the app, then it is
* provided to the DRM engine plugin using provideKeyResponse. When the
* response is for an offline key request, a keySetId is returned that can be
diff --git a/media/ndk/libmediandk.map.txt b/media/ndk/libmediandk.map.txt
index 6f275c7..b228945 100644
--- a/media/ndk/libmediandk.map.txt
+++ b/media/ndk/libmediandk.map.txt
@@ -229,6 +229,7 @@
AMediaDrm_decrypt;
AMediaDrm_encrypt;
AMediaDrm_getKeyRequest;
+ AMediaDrm_getKeyRequestWithDefaultUrlAndType; # introduced=Tiramisu
AMediaDrm_getPropertyByteArray;
AMediaDrm_getPropertyString;
AMediaDrm_getProvisionRequest;
diff --git a/services/audioflinger/PatchPanel.cpp b/services/audioflinger/PatchPanel.cpp
index 93118b8..45dd258 100644
--- a/services/audioflinger/PatchPanel.cpp
+++ b/services/audioflinger/PatchPanel.cpp
@@ -575,6 +575,12 @@
// create a special playback track to render to playback thread.
// this track is given the same buffer as the PatchRecord buffer
+
+ // Default behaviour is to start as soon as possible to have the lowest possible latency even if
+ // it might glitch.
+ // Disable this behavior for FM Tuner source if no fast capture/mixer available.
+ const bool isFmBridge = mAudioPatch.sources[0].ext.device.type == AUDIO_DEVICE_IN_FM_TUNER;
+ const size_t frameCountToBeReady = isFmBridge && !usePassthruPatchRecord ? frameCount / 4 : 1;
sp<PlaybackThread::PatchTrack> tempPatchTrack = new PlaybackThread::PatchTrack(
mPlayback.thread().get(),
streamType,
@@ -584,7 +590,9 @@
frameCount,
tempRecordTrack->buffer(),
tempRecordTrack->bufferSize(),
- outputFlags);
+ outputFlags,
+ {} /*timeout*/,
+ frameCountToBeReady);
status = mPlayback.checkTrack(tempPatchTrack.get());
if (status != NO_ERROR) {
return status;
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index 3cce998..aecd4d3 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -193,6 +193,12 @@
}
}
+ static bool checkServerLatencySupported(
+ audio_format_t format, audio_output_flags_t flags) {
+ return audio_is_linear_pcm(format)
+ && (flags & AUDIO_OUTPUT_FLAG_HW_AV_SYNC) == 0;
+ }
+
audio_output_flags_t getOutputFlags() const { return mFlags; }
float getSpeed() const { return mSpeed; }
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 616fd78..233865f 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -713,8 +713,7 @@
thread->mFastTrackAvailMask &= ~(1 << i);
}
- mServerLatencySupported = thread->type() == ThreadBase::MIXER
- || thread->type() == ThreadBase::DUPLICATING;
+ mServerLatencySupported = checkServerLatencySupported(format, flags);
#ifdef TEE_SINK
mTee.setId(std::string("_") + std::to_string(mThreadIoHandle)
+ "_" + std::to_string(mId) + "_T");
diff --git a/services/audiopolicy/engineconfigurable/src/Engine.cpp b/services/audiopolicy/engineconfigurable/src/Engine.cpp
index b0c376a..9a61a05 100644
--- a/services/audiopolicy/engineconfigurable/src/Engine.cpp
+++ b/services/audiopolicy/engineconfigurable/src/Engine.cpp
@@ -299,8 +299,13 @@
if (device != nullptr) {
return DeviceVector(device);
}
+ return fromCache? getCachedDevices(strategy) : getDevicesForProductStrategy(strategy);
+}
- return fromCache? mDevicesForStrategies.at(strategy) : getDevicesForProductStrategy(strategy);
+DeviceVector Engine::getCachedDevices(product_strategy_t ps) const
+{
+ return mDevicesForStrategies.find(ps) != mDevicesForStrategies.end() ?
+ mDevicesForStrategies.at(ps) : DeviceVector{};
}
DeviceVector Engine::getOutputDevicesForStream(audio_stream_type_t stream, bool fromCache) const
diff --git a/services/audiopolicy/engineconfigurable/src/Engine.h b/services/audiopolicy/engineconfigurable/src/Engine.h
index d8e2742..f665da5 100644
--- a/services/audiopolicy/engineconfigurable/src/Engine.h
+++ b/services/audiopolicy/engineconfigurable/src/Engine.h
@@ -126,6 +126,7 @@
status_t loadAudioPolicyEngineConfig();
DeviceVector getDevicesForProductStrategy(product_strategy_t strategy) const;
+ DeviceVector getCachedDevices(product_strategy_t ps) const;
/**
* Policy Parameter Manager hidden through a wrapper.
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 00c1f26..05eae98 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -1702,6 +1702,8 @@
// The priority is as follows:
// 1: the output supporting haptic playback when requesting haptic playback
// 2: the output with the highest number of requested functional flags
+ // with tiebreak preferring the minimum number of extra functional flags
+ // (see b/200293124, the incorrect selection of AUDIO_OUTPUT_FLAG_VOIP_RX).
// 3: the output supporting the exact channel mask
// 4: the output with a higher channel count than requested
// 5: the output with a higher sampling rate than requested
@@ -1743,7 +1745,12 @@
}
// functional flags match
- currentMatchCriteria[1] = popcount(outputDesc->mFlags & functionalFlags);
+ const int matchingFunctionalFlags =
+ __builtin_popcount(outputDesc->mFlags & functionalFlags);
+ const int totalFunctionalFlags =
+ __builtin_popcount(outputDesc->mFlags & kFunctionalFlags);
+ // Prefer matching functional flags, but subtract unnecessary functional flags.
+ currentMatchCriteria[1] = 100 * (matchingFunctionalFlags + 1) - totalFunctionalFlags;
// channel mask and channel count match
uint32_t outputChannelCount = audio_channel_count_from_out_mask(
@@ -6103,7 +6110,8 @@
return hasVoiceStream(streams) && (outputDesc == mPrimaryOutput ||
outputDesc->isActive(toVolumeSource(AUDIO_STREAM_VOICE_CALL))) &&
(isInCall() ||
- mOutputs.isStrategyActiveOnSameModule(productStrategy, outputDesc));
+ mOutputs.isStrategyActiveOnSameModule(productStrategy, outputDesc)) &&
+ !isStreamActive(AUDIO_STREAM_ENFORCED_AUDIBLE, 0);
};
// With low-latency playing on speaker, music on WFD, when the first low-latency
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 5a18582..b3db66b 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -4453,12 +4453,20 @@
if (dumpVector.empty()) { return; }
+ std::string dumpString;
+
+ String8 currentTime = getFormattedCurrentTime();
+ dumpString += "Cached @ ";
+ dumpString += currentTime.string();
+ dumpString += "\n"; // First line is the timestamp of when client is cached.
+
+
const String16 &packageName = client->getPackageName();
String8 packageName8 = String8(packageName);
const char *printablePackageName = packageName8.lockBuffer(packageName.size());
- std::string dumpString;
+
size_t i = dumpVector.size();
// Store the string in reverse order (latest last)
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.h b/services/camera/libcameraservice/common/CameraProviderManager.h
index 7d13941..b03ca62 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.h
+++ b/services/camera/libcameraservice/common/CameraProviderManager.h
@@ -552,8 +552,10 @@
mName(name), mId(id), mVersion(version), mProviderTagid(tagId),
mIsLogicalCamera(false), mResourceCost(resourceCost),
mStatus(hardware::camera::common::V1_0::CameraDeviceStatus::PRESENT),
- mParentProvider(parentProvider), mHasFlashUnit(false),
- mSupportNativeZoomRatio(false), mPublicCameraIds(publicCameraIds) {}
+ mParentProvider(parentProvider), mTorchStrengthLevel(0),
+ mTorchMaximumStrengthLevel(0), mTorchDefaultStrengthLevel(0),
+ mHasFlashUnit(false), mSupportNativeZoomRatio(false),
+ mPublicCameraIds(publicCameraIds) {}
virtual ~DeviceInfo();
protected:
bool mHasFlashUnit; // const after constructor
diff --git a/services/mediametrics/Android.bp b/services/mediametrics/Android.bp
index 74e4715..c98d5fc 100644
--- a/services/mediametrics/Android.bp
+++ b/services/mediametrics/Android.bp
@@ -185,6 +185,10 @@
"libplatformprotos",
],
+ header_libs: [
+ "libaaudio_headers",
+ ],
+
include_dirs: [
"system/media/audio_utils/include",
],
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index 270fe2f..46c701c 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -21,6 +21,7 @@
#include "AudioAnalytics.h"
+#include <aaudio/AAudio.h> // error codes
#include <audio_utils/clock.h> // clock conversions
#include <cutils/properties.h>
#include <statslog.h> // statsd
@@ -64,6 +65,50 @@
}
}
+// The status variable contains status_t codes which are used by
+// the core audio framework. We also consider AAudio status codes.
+//
+// Compare with mediametrics::statusToStatusString
+//
+inline constexpr const char* extendedStatusToStatusString(status_t status) {
+ switch (status) {
+ case BAD_VALUE: // status_t
+ case AAUDIO_ERROR_ILLEGAL_ARGUMENT:
+ case AAUDIO_ERROR_INVALID_FORMAT:
+ case AAUDIO_ERROR_INVALID_RATE:
+ case AAUDIO_ERROR_NULL:
+ case AAUDIO_ERROR_OUT_OF_RANGE:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT;
+ case DEAD_OBJECT: // status_t
+ case FAILED_TRANSACTION: // status_t
+ case AAUDIO_ERROR_DISCONNECTED:
+ case AAUDIO_ERROR_INVALID_HANDLE:
+ case AAUDIO_ERROR_NO_SERVICE:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_IO;
+ case NO_MEMORY: // status_t
+ case AAUDIO_ERROR_NO_FREE_HANDLES:
+ case AAUDIO_ERROR_NO_MEMORY:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_MEMORY;
+ case PERMISSION_DENIED: // status_t
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_SECURITY;
+ case INVALID_OPERATION: // status_t
+ case NO_INIT: // status_t
+ case AAUDIO_ERROR_INVALID_STATE:
+ case AAUDIO_ERROR_UNAVAILABLE:
+ case AAUDIO_ERROR_UNIMPLEMENTED:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_STATE;
+ case WOULD_BLOCK: // status_t
+ case AAUDIO_ERROR_TIMEOUT:
+ case AAUDIO_ERROR_WOULD_BLOCK:
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_TIMEOUT;
+ default:
+ if (status >= 0) return AMEDIAMETRICS_PROP_STATUS_VALUE_OK; // non-negative values "OK"
+ [[fallthrough]]; // negative values are error.
+ case UNKNOWN_ERROR: // status_t
+ return AMEDIAMETRICS_PROP_STATUS_VALUE_UNKNOWN;
+ }
+}
+
static constexpr const auto LOG_LEVEL = android::base::VERBOSE;
static constexpr int PREVIOUS_STATE_EXPIRE_SEC = 60 * 60; // 1 hour.
@@ -392,11 +437,15 @@
{
if (!startsWith(item->getKey(), AMEDIAMETRICS_KEY_PREFIX_AUDIO)) return BAD_VALUE;
status_t status = mAnalyticsState->submit(item, isTrusted);
+
+ // Status is selectively authenticated.
+ processStatus(item);
+
if (status != NO_ERROR) return status; // may not be permitted.
// Only if the item was successfully submitted (permission)
// do we check triggered actions.
- checkActions(item);
+ processActions(item);
return NO_ERROR;
}
@@ -430,7 +479,7 @@
return { ss.str(), lines - ll };
}
-void AudioAnalytics::checkActions(const std::shared_ptr<const mediametrics::Item>& item)
+void AudioAnalytics::processActions(const std::shared_ptr<const mediametrics::Item>& item)
{
auto actions = mActions.getActionsForItem(item); // internally locked.
// Execute actions with no lock held.
@@ -439,6 +488,36 @@
}
}
+void AudioAnalytics::processStatus(const std::shared_ptr<const mediametrics::Item>& item)
+{
+ int32_t status;
+ if (!item->get(AMEDIAMETRICS_PROP_STATUS, &status)) return;
+
+ // Any record with a status will automatically be added to a heat map.
+ // Standard information.
+ const auto key = item->getKey();
+ const auto uid = item->getUid();
+
+ // from audio.track.10 -> prefix = audio.track, suffix = 10
+ // from audio.track.error -> prefix = audio.track, suffix = error
+ const auto [prefixKey, suffixKey] = stringutils::splitPrefixKey(key);
+
+ std::string message;
+ item->get(AMEDIAMETRICS_PROP_STATUSMESSAGE, &message); // optional
+
+ int32_t subCode = 0; // not used
+ (void)item->get(AMEDIAMETRICS_PROP_STATUSSUBCODE, &subCode); // optional
+
+ std::string eventStr; // optional
+ item->get(AMEDIAMETRICS_PROP_EVENT, &eventStr);
+
+ const std::string statusString = extendedStatusToStatusString(status);
+
+ // Add to the heat map - we automatically track every item's status to see
+ // the types of errors and the frequency of errors.
+ mHeatMap.add(prefixKey, suffixKey, eventStr, statusString, uid, message, subCode);
+}
+
// HELPER METHODS
std::string AudioAnalytics::getThreadFromTrack(const std::string& track) const
diff --git a/services/mediametrics/AudioAnalytics.h b/services/mediametrics/AudioAnalytics.h
index 2b41a95..9b54cf3 100644
--- a/services/mediametrics/AudioAnalytics.h
+++ b/services/mediametrics/AudioAnalytics.h
@@ -20,6 +20,7 @@
#include "AnalyticsActions.h"
#include "AnalyticsState.h"
#include "AudioPowerUsage.h"
+#include "HeatMap.h"
#include "StatsdLog.h"
#include "TimedAction.h"
#include "Wrap.h"
@@ -73,11 +74,23 @@
std::pair<std::string, int32_t> dump(
int32_t lines = INT32_MAX, int64_t sinceNs = 0, const char *prefix = nullptr) const;
+ /**
+ * Returns a pair consisting of the dump string and the number of lines in the string.
+ *
+ * HeatMap dump.
+ */
+ std::pair<std::string, int32_t> dumpHeatMap(int32_t lines = INT32_MAX) const {
+ return mHeatMap.dump(lines);
+ }
+
void clear() {
// underlying state is locked.
mPreviousAnalyticsState->clear();
mAnalyticsState->clear();
+ // Clears the status map
+ mHeatMap.clear();
+
// Clear power usage state.
mAudioPowerUsage.clear();
}
@@ -96,11 +109,18 @@
*/
/**
- * Checks for any pending actions for a particular item.
+ * Processes any pending actions for a particular item.
*
* \param item to check against the current AnalyticsActions.
*/
- void checkActions(const std::shared_ptr<const mediametrics::Item>& item);
+ void processActions(const std::shared_ptr<const mediametrics::Item>& item);
+
+ /**
+ * Processes status information contained in the item.
+ *
+ * \param item to check against for status handling
+ */
+ void processStatus(const std::shared_ptr<const mediametrics::Item>& item);
// HELPER METHODS
/**
@@ -124,6 +144,9 @@
TimedAction mTimedAction; // locked internally
const std::shared_ptr<StatsdLog> mStatsdLog; // locked internally, ok for multiple threads.
+ static constexpr size_t kHeatEntries = 100;
+ HeatMap mHeatMap{kHeatEntries}; // locked internally, ok for multiple threads.
+
// DeviceUse is a nested class which handles audio device usage accounting.
// We define this class at the end to ensure prior variables all properly constructed.
// TODO: Track / Thread interaction
diff --git a/services/mediametrics/AudioTypes.cpp b/services/mediametrics/AudioTypes.cpp
index 838cdd5..b67967b 100644
--- a/services/mediametrics/AudioTypes.cpp
+++ b/services/mediametrics/AudioTypes.cpp
@@ -15,8 +15,10 @@
*/
#include "AudioTypes.h"
+#include "MediaMetricsConstants.h"
#include "StringUtils.h"
#include <media/TypeConverter.h> // requires libmedia_helper to get the Audio code.
+#include <statslog.h> // statsd
namespace android::mediametrics::types {
diff --git a/services/mediametrics/HeatMap.h b/services/mediametrics/HeatMap.h
new file mode 100644
index 0000000..950501a
--- /dev/null
+++ b/services/mediametrics/HeatMap.h
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#pragma once
+
+#include <iomanip>
+#include <map>
+#include <sstream>
+#include "MediaMetricsConstants.h"
+
+namespace android::mediametrics {
+
+/**
+ * HeatData accumulates statistics on the status reported for a given key.
+ *
+ * HeatData is a helper class used by HeatMap to represent statistics. We expose it
+ * here for testing purposes currently.
+ *
+ * Note: This class is not thread safe, so mutual exclusion should be obtained by the caller
+ * which in this case is HeatMap. HeatMap getData() returns a local copy of HeatData, so use
+ * of that is thread-safe.
+ */
+class HeatData {
+ /* HeatData for a key is stored in a map based on the event (e.g. "start", "pause", create)
+ * and then another map based on the status (e.g. "ok", "argument", "state").
+ */
+ std::map<std::string /* event */,
+ std::map<std::string /* status name */, size_t /* count, nonzero */>> mMap;
+
+public:
+ /**
+ * Add status data.
+ *
+ * \param suffix (ignored) the suffix to the key that was stripped, if any.
+ * \param event the event (e.g. create, start, pause, stop, etc.).
+ * \param uid (ignored) the uid associated with the error.
+ * \param message (ignored) the status message, if any.
+ * \param subCode (ignored) the status subcode, if any.
+ */
+ void add(const std::string& suffix, const std::string& event, const std::string& status,
+ uid_t uid, const std::string& message, int32_t subCode) {
+ // Perhaps there could be a more detailed print.
+ (void)suffix;
+ (void)uid;
+ (void)message;
+ (void)subCode;
+ ++mMap[event][status];
+ }
+
+ /** Returns the number of event names with status. */
+ size_t size() const {
+ return mMap.size();
+ }
+
+ /**
+ * Returns a deque with pairs indicating the count of Oks and Errors.
+ * The first pair is total, the other pairs are in order of mMap.
+ *
+ * Example return value of {ok, error} pairs:
+ * total key1 key2
+ * { { 2, 1 }, { 1, 0 }, { 1, 1 } }
+ */
+ std::deque<std::pair<size_t /* oks */, size_t /* errors */>> heatCount() const {
+ size_t totalOk = 0;
+ size_t totalError = 0;
+ std::deque<std::pair<size_t /* oks */, size_t /* errors */>> heat;
+ for (const auto &eventPair : mMap) {
+ size_t ok = 0;
+ size_t error = 0;
+ for (const auto &[name, count] : eventPair.second) {
+ if (name == AMEDIAMETRICS_PROP_STATUS_VALUE_OK) {
+ ok += count;
+ } else {
+ error += count;
+ }
+ }
+ totalOk += ok;
+ totalError += error;
+ heat.emplace_back(ok, error);
+ }
+ heat.emplace_front(totalOk, totalError);
+ return heat;
+ }
+
+ /** Returns the error fraction from a pair <oks, errors>, a float between 0.f to 1.f. */
+ static float fraction(const std::pair<size_t, size_t>& count) {
+ return (float)count.second / (count.first + count.second);
+ }
+
+ /** Returns the HeatMap information in a single line string. */
+ std::string dump() const {
+ const auto heat = heatCount();
+ auto it = heat.begin();
+ std::stringstream ss;
+ ss << "{ ";
+ float errorFraction = fraction(*it++);
+ if (errorFraction > 0.f) {
+ ss << std::fixed << std::setprecision(2) << errorFraction << " ";
+ }
+ for (const auto &eventPair : mMap) {
+ ss << eventPair.first << ": { ";
+ errorFraction = fraction(*it++);
+ if (errorFraction > 0.f) {
+ ss << std::fixed << std::setprecision(2) << errorFraction << " ";
+ }
+ for (const auto &[name, count]: eventPair.second) {
+ ss << "[ " << name << " : " << count << " ] ";
+ }
+ ss << "} ";
+ }
+ ss << " }";
+ return ss.str();
+ }
+};
+
+/**
+ * HeatMap is a thread-safe collection that counts activity of status errors per key.
+ *
+ * The classic heat map is a 2D picture with intensity shown by color.
+ * Here we accumulate the status results from keys to see if there are consistent
+ * failures in the system.
+ *
+ * TODO(b/210855555): Heatmap improvements.
+ * 1) heat decays in intensity in time for past events, currently we don't decay.
+ */
+
+class HeatMap {
+ const size_t mMaxSize;
+ mutable std::mutex mLock;
+ size_t mRejected GUARDED_BY(mLock) = 0;
+ std::map<std::string, HeatData> mMap GUARDED_BY(mLock);
+
+public:
+ /**
+ * Constructs a HeatMap.
+ *
+ * \param maxSize the maximum number of elements that are tracked.
+ */
+ explicit HeatMap(size_t maxSize) : mMaxSize(maxSize) {
+ }
+
+ /** Returns the number of keys. */
+ size_t size() const {
+ std::lock_guard l(mLock);
+ return mMap.size();
+ }
+
+ /** Clears error history. */
+ void clear() {
+ std::lock_guard l(mLock);
+ return mMap.clear();
+ }
+
+ /** Returns number of keys rejected due to space. */
+ size_t rejected() const {
+ std::lock_guard l(mLock);
+ return mRejected;
+ }
+
+ /** Returns a copy of the heat data associated with key. */
+ HeatData getData(const std::string& key) const {
+ std::lock_guard l(mLock);
+ return mMap.count(key) == 0 ? HeatData{} : mMap.at(key);
+ }
+
+ /**
+ * Adds a new entry.
+ * \param key the key category (e.g. audio.track).
+ * \param suffix (ignored) the suffix to the key that was stripped, if any.
+ * \param event the event (e.g. create, start, pause, stop, etc.).
+ * \param uid (ignored) the uid associated with the error.
+ * \param message (ignored) the status message, if any.
+ * \param subCode (ignored) the status subcode, if any.
+ */
+ void add(const std::string& key, const std::string& suffix, const std::string& event,
+ const std::string& status, uid_t uid, const std::string& message, int32_t subCode) {
+ std::lock_guard l(mLock);
+
+ // Hard limit on heat map entries.
+ // TODO: have better GC.
+ if (mMap.size() == mMaxSize && mMap.count(key) == 0) {
+ ++mRejected;
+ return;
+ }
+ mMap[key].add(suffix, event, status, uid, message, subCode);
+ }
+
+ /**
+ * Returns a pair consisting of the dump string and the number of lines in the string.
+ */
+ std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const {
+ std::stringstream ss;
+ int32_t ll = lines;
+ std::lock_guard l(mLock);
+ if (ll > 0) {
+ ss << "Error Heat Map (rejected: " << mRejected << "):\n";
+ --ll;
+ }
+ // TODO: restriction is implemented alphabetically not on priority.
+ for (const auto& [name, data] : mMap) {
+ if (ll <= 0) break;
+ ss << name << ": " << data.dump() << "\n";
+ --ll;
+ }
+ return { ss.str(), lines - ll };
+ }
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
index 35e0ae4..636b343 100644
--- a/services/mediametrics/MediaMetricsService.cpp
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -319,11 +319,19 @@
result << "-- some lines may be truncated --\n";
}
- result << "LogSessionId:\n"
+ const int32_t heatLinesToDump = all ? INT32_MAX : 20;
+ const auto [ heatDumpString, heatLines] =
+ mAudioAnalytics.dumpHeatMap(heatLinesToDump);
+ result << "\n" << heatDumpString;
+ if (heatLines == heatLinesToDump) {
+ result << "-- some lines may be truncated --\n";
+ }
+
+ result << "\nLogSessionId:\n"
<< mediametrics::ValidateId::get()->dump();
// Dump the statsd atoms we sent out.
- result << "Statsd atoms:\n"
+ result << "\nStatsd atoms:\n"
<< mStatsdLog->dumpToString(" " /* prefix */,
all ? STATSD_LOG_LINES_MAX : STATSD_LOG_LINES_DUMP);
}
diff --git a/services/mediametrics/StringUtils.h b/services/mediametrics/StringUtils.h
index 01034d9..a56f5b8 100644
--- a/services/mediametrics/StringUtils.h
+++ b/services/mediametrics/StringUtils.h
@@ -167,4 +167,41 @@
return ss.str();
}
+/**
+ * Returns true if the string is non-null, not empty, and contains only digits.
+ */
+inline constexpr bool isNumeric(const char *s)
+{
+ if (s == nullptr || *s == 0) return false;
+ do {
+ if (!isdigit(*s)) return false;
+ } while (*++s != 0);
+ return true; // all digits
+}
+
+/**
+ * Extracts out the prefix from the key, returning a pair of prefix, suffix.
+ *
+ * Usually the key is something like:
+ * Prefix.(ID)
+ * where ID is an integer,
+ * or "error" if the id was not returned because of failure,
+ * or "status" if general status.
+ *
+ * Example: audio.track.10 -> prefix = audio.track, suffix = 10
+ * audio.track.error -> prefix = audio.track, suffix = error
+ * audio.track.status -> prefix = audio.track, suffix = status
+ * audio.mute -> prefix = audio.mute, suffix = ""
+ */
+inline std::pair<std::string /* prefix */,
+ std::string /* suffix */> splitPrefixKey(const std::string &key)
+{
+ const size_t split = key.rfind('.');
+ const char* suffix = key.c_str() + split + 1;
+ if (*suffix && (!strcmp(suffix, "error") || !strcmp(suffix, "status") || isNumeric(suffix))) {
+ return { key.substr(0, split), suffix };
+ }
+ return { key, "" };
+}
+
} // namespace android::mediametrics::stringutils
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index cd6af9f..102700a 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -1226,8 +1226,8 @@
}
}
-TEST(mediametrics_tests, ErrorConversion) {
- constexpr status_t errors[] = {
+TEST(mediametrics_tests, StatusConversion) {
+ constexpr status_t statuses[] = {
NO_ERROR,
BAD_VALUE,
DEAD_OBJECT,
@@ -1239,15 +1239,58 @@
};
auto roundTrip = [](status_t status) {
- return android::mediametrics::errorStringToStatus(
- android::mediametrics::statusToErrorString(status));
+ return android::mediametrics::statusStringToStatus(
+ android::mediametrics::statusToStatusString(status));
};
// Primary status error categories.
- for (const auto error : errors) {
- ASSERT_EQ(error, roundTrip(error));
+ for (const auto status : statuses) {
+ ASSERT_EQ(status, roundTrip(status));
}
// Status errors specially considered.
ASSERT_EQ(DEAD_OBJECT, roundTrip(FAILED_TRANSACTION));
}
+
+TEST(mediametrics_tests, HeatMap) {
+ constexpr size_t SIZE = 2;
+ android::mediametrics::HeatMap heatMap{SIZE};
+ constexpr uid_t UID = 0;
+ constexpr int32_t SUBCODE = 1;
+
+ ASSERT_EQ((size_t)0, heatMap.size());
+ heatMap.add("someKey", "someSuffix", "someEvent",
+ AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
+ ASSERT_EQ((size_t)1, heatMap.size());
+ heatMap.add("someKey", "someSuffix", "someEvent",
+ AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
+ heatMap.add("someKey", "someSuffix", "anotherEvent",
+ AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT, UID, "message", SUBCODE);
+ ASSERT_EQ((size_t)1, heatMap.size());
+ heatMap.add("anotherKey", "someSuffix", "someEvent",
+ AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
+ ASSERT_EQ((size_t)2, heatMap.size());
+ ASSERT_EQ((size_t)0, heatMap.rejected());
+
+ heatMap.add("thirdKey", "someSuffix", "someEvent",
+ AMEDIAMETRICS_PROP_STATUS_VALUE_OK, UID, "message", SUBCODE);
+ ASSERT_EQ((size_t)2, heatMap.size());
+ ASSERT_EQ((size_t)1, heatMap.rejected());
+
+ android::mediametrics::HeatData heatData = heatMap.getData("someKey");
+ ASSERT_EQ((size_t)2, heatData.size());
+ auto count = heatData.heatCount();
+ ASSERT_EQ((size_t)3, count.size()); // pairs in order { total, "anotherEvent", "someEvent" }
+ // check total value
+ ASSERT_EQ((size_t)2, count[0].first); // OK
+ ASSERT_EQ((size_t)1, count[0].second); // ERROR;
+ // first key "anotherEvent"
+ ASSERT_EQ((size_t)0, count[1].first); // OK
+ ASSERT_EQ((size_t)1, count[1].second); // ERROR;
+ // second key "someEvent"
+ ASSERT_EQ((size_t)2, count[2].first); // OK
+ ASSERT_EQ((size_t)0, count[2].second); // ERROR;
+
+ heatMap.clear();
+ ASSERT_EQ((size_t)0, heatMap.size());
+}
diff --git a/services/oboeservice/AAudioCommandQueue.cpp b/services/oboeservice/AAudioCommandQueue.cpp
index ddaabe8..9bd18b3 100644
--- a/services/oboeservice/AAudioCommandQueue.cpp
+++ b/services/oboeservice/AAudioCommandQueue.cpp
@@ -28,6 +28,10 @@
aaudio_result_t AAudioCommandQueue::sendCommand(std::shared_ptr<AAudioCommand> command) {
{
std::scoped_lock<std::mutex> _l(mLock);
+ if (!mRunning) {
+ ALOGE("Tried to send command while it was not running");
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
mCommands.push(command);
mWaitWorkCond.notify_one();
}
@@ -68,7 +72,7 @@
return !mRunning || !mCommands.empty();
});
}
- if (!mCommands.empty()) {
+ if (!mCommands.empty() && mRunning) {
command = mCommands.front();
mCommands.pop();
}
@@ -76,9 +80,27 @@
return command;
}
+void AAudioCommandQueue::startWaiting() {
+ std::scoped_lock<std::mutex> _l(mLock);
+ mRunning = true;
+}
+
void AAudioCommandQueue::stopWaiting() {
std::scoped_lock<std::mutex> _l(mLock);
mRunning = false;
+ // Clear all commands in the queue as the command thread is stopped.
+ while (!mCommands.empty()) {
+ auto command = mCommands.front();
+ mCommands.pop();
+ std::scoped_lock<std::mutex> _cl(command->lock);
+ // If the command is waiting for result, returns AAUDIO_ERROR_INVALID_STATE
+ // as there is no thread waiting for the command.
+ if (command->isWaitingForReply) {
+ command->result = AAUDIO_ERROR_INVALID_STATE;
+ command->isWaitingForReply = false;
+ command->conditionVariable.notify_one();
+ }
+ }
mWaitWorkCond.notify_one();
}
diff --git a/services/oboeservice/AAudioCommandQueue.h b/services/oboeservice/AAudioCommandQueue.h
index 5f25507..64442a3 100644
--- a/services/oboeservice/AAudioCommandQueue.h
+++ b/services/oboeservice/AAudioCommandQueue.h
@@ -78,6 +78,12 @@
std::shared_ptr<AAudioCommand> waitForCommand(int64_t timeoutNanos = -1);
/**
+ * Start waiting for commands. Commands can only be pushed into the command queue after it
+ * starts waiting.
+ */
+ void startWaiting();
+
+ /**
* Force stop waiting for next command
*/
void stopWaiting();
@@ -87,7 +93,7 @@
std::condition_variable mWaitWorkCond;
std::queue<std::shared_ptr<AAudioCommand>> mCommands GUARDED_BY(mLock);
- bool mRunning GUARDED_BY(mLock) = true;
+ bool mRunning GUARDED_BY(mLock) = false;
};
} // namespace aaudio
\ No newline at end of file
diff --git a/services/oboeservice/AAudioServiceEndpointMMAP.cpp b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
index b9c1260..046b84b 100644
--- a/services/oboeservice/AAudioServiceEndpointMMAP.cpp
+++ b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
@@ -242,6 +242,14 @@
setFormat(config.format);
setSampleRate(config.sample_rate);
+ // If the position is not updated while the timestamp is updated for more than a certain amount,
+ // the timestamp reported from the HAL may not be accurate. Here, a timestamp grace period is
+ // set as 5 burst size. We may want to update this value if there is any report from OEMs saying
+ // that is too short.
+ static constexpr int kTimestampGraceBurstCount = 5;
+ mTimestampGracePeriodMs = ((int64_t) kTimestampGraceBurstCount * mFramesPerBurst
+ * AAUDIO_MILLIS_PER_SECOND) / getSampleRate();
+
ALOGD("%s() actual rate = %d, channels = %d channelMask = %#x, deviceId = %d, capacity = %d\n",
__func__, getSampleRate(), getSamplesPerFrame(), getChannelMask(),
deviceId, getBufferCapacity());
@@ -418,14 +426,79 @@
aaudio_result_t AAudioServiceEndpointMMAP::getExternalPosition(uint64_t *positionFrames,
int64_t *timeNanos)
{
- if (!mExternalPositionSupported) {
- return AAUDIO_ERROR_INVALID_STATE;
+ if (mHalExternalPositionStatus != AAUDIO_OK) {
+ return mHalExternalPositionStatus;
}
- status_t status = mMmapStream->getExternalPosition(positionFrames, timeNanos);
- if (status == INVALID_OPERATION) {
- // getExternalPosition is not supported. Set mExternalPositionSupported as false
+ uint64_t tempPositionFrames;
+ int64_t tempTimeNanos;
+ status_t status = mMmapStream->getExternalPosition(&tempPositionFrames, &tempTimeNanos);
+ if (status != OK) {
+ // getExternalPosition reports error. The HAL may not support the API. Cache the result
// so that the call will not go to the HAL next time.
- mExternalPositionSupported = false;
+ mHalExternalPositionStatus = AAudioConvert_androidToAAudioResult(status);
+ return mHalExternalPositionStatus;
}
- return AAudioConvert_androidToAAudioResult(status);
+
+ // If the HAL keeps reporting the same position or timestamp, the HAL may be having some issues
+ // to report correct external position. In that case, we will not trust the values reported from
+ // the HAL. Ideally, we may want to stop querying external position if the HAL cannot report
+ // correct position within a period. But it may not be a good idea to get system time too often.
+ // In that case, a maximum number of frozen external position is defined so that if the
+ // count of the same timestamp or position is reported by the HAL continuously, the values from
+ // the HAL will no longer be trusted.
+ static constexpr int kMaxFrozenCount = 20;
+ // If the HAL version is less than 7.0, the getPresentationPosition is an optional API.
+ // If the HAL version is 7.0 or later, the getPresentationPosition is a mandatory API.
+ // In that case, even the returned status is NO_ERROR, it doesn't indicate the returned
+ // position is a valid one. Do a simple validation, which is checking if the position is
+ // forward within half a second or not, here so that this function can return error if
+ // the validation fails. Note that we don't only apply this validation logic to HAL API
+ // less than 7.0. The reason is that there is a chance the HAL is not reporting the
+ // timestamp and position correctly.
+ if (mLastPositionFrames > tempPositionFrames) {
+ // If the position is going backwards, there must be something wrong with the HAL.
+ // In that case, we do not trust the values reported by the HAL.
+ ALOGW("%s position is going backwards, last position(%jd) current position(%jd)",
+ __func__, mLastPositionFrames, tempPositionFrames);
+ mHalExternalPositionStatus = AAUDIO_ERROR_INTERNAL;
+ return mHalExternalPositionStatus;
+ } else if (mLastPositionFrames == tempPositionFrames) {
+ if (tempTimeNanos - mTimestampNanosForLastPosition >
+ AAUDIO_NANOS_PER_MILLISECOND * mTimestampGracePeriodMs) {
+ ALOGW("%s, the reported position is not changed within %d msec. "
+ "Set the external position as not supported", __func__, mTimestampGracePeriodMs);
+ mHalExternalPositionStatus = AAUDIO_ERROR_INTERNAL;
+ return mHalExternalPositionStatus;
+ }
+ mFrozenPositionCount++;
+ } else {
+ mFrozenPositionCount = 0;
+ }
+
+ if (mTimestampNanosForLastPosition > tempTimeNanos) {
+ // If the timestamp is going backwards, there must be something wrong with the HAL.
+ // In that case, we do not trust the values reported by the HAL.
+ ALOGW("%s timestamp is going backwards, last timestamp(%jd), current timestamp(%jd)",
+ __func__, mTimestampNanosForLastPosition, tempTimeNanos);
+ mHalExternalPositionStatus = AAUDIO_ERROR_INTERNAL;
+ return mHalExternalPositionStatus;
+ } else if (mTimestampNanosForLastPosition == tempTimeNanos) {
+ mFrozenTimestampCount++;
+ } else {
+ mFrozenTimestampCount = 0;
+ }
+
+ if (mFrozenTimestampCount + mFrozenPositionCount > kMaxFrozenCount) {
+ ALOGW("%s too many frozen external position from HAL.", __func__);
+ mHalExternalPositionStatus = AAUDIO_ERROR_INTERNAL;
+ return mHalExternalPositionStatus;
+ }
+
+ mLastPositionFrames = tempPositionFrames;
+ mTimestampNanosForLastPosition = tempTimeNanos;
+
+ // Only update the timestamp and position when they looks valid.
+ *positionFrames = tempPositionFrames;
+ *timeNanos = tempTimeNanos;
+ return mHalExternalPositionStatus;
}
diff --git a/services/oboeservice/AAudioServiceEndpointMMAP.h b/services/oboeservice/AAudioServiceEndpointMMAP.h
index ddfac63..6314e5e 100644
--- a/services/oboeservice/AAudioServiceEndpointMMAP.h
+++ b/services/oboeservice/AAudioServiceEndpointMMAP.h
@@ -106,7 +106,12 @@
int64_t mHardwareTimeOffsetNanos = 0; // TODO get from HAL
- bool mExternalPositionSupported = true;
+ aaudio_result_t mHalExternalPositionStatus = AAUDIO_OK;
+ uint64_t mLastPositionFrames = 0;
+ int64_t mTimestampNanosForLastPosition = 0;
+ int32_t mTimestampGracePeriodMs;
+ int32_t mFrozenPositionCount = 0;
+ int32_t mFrozenTimestampCount = 0;
};
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index a25a791..8b5ccaa 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -52,7 +52,6 @@
, mAtomicStreamTimestamp()
, mAudioService(audioService) {
mMmapClient.attributionSource = AttributionSourceState();
- mThreadEnabled = true;
}
AAudioServiceStreamBase::~AAudioServiceStreamBase() {
@@ -178,6 +177,7 @@
// Make sure this object does not get deleted before the run() method
// can protect it by making a strong pointer.
+ mCommandQueue.startWaiting();
mThreadEnabled = true;
incStrong(nullptr); // See run() method.
result = mCommandThread.start(this);
@@ -188,14 +188,15 @@
return result;
error:
- close();
+ closeAndClear();
+ mThreadEnabled = false;
+ mCommandQueue.stopWaiting();
+ mCommandThread.stop();
return result;
}
aaudio_result_t AAudioServiceStreamBase::close() {
- auto command = std::make_shared<AAudioCommand>(
- CLOSE, nullptr, true /*waitForReply*/, TIMEOUT_NANOS);
- aaudio_result_t result = mCommandQueue.sendCommand(command);
+ aaudio_result_t result = sendCommand(CLOSE, nullptr, true /*waitForReply*/, TIMEOUT_NANOS);
// Stop the command thread as the stream is closed.
mThreadEnabled = false;
@@ -213,25 +214,7 @@
// This will stop the stream, just in case it was not already stopped.
stop_l();
- aaudio_result_t result = AAUDIO_OK;
- sp<AAudioServiceEndpoint> endpoint = mServiceEndpointWeak.promote();
- if (endpoint == nullptr) {
- result = AAUDIO_ERROR_INVALID_STATE;
- } else {
- endpoint->unregisterStream(this);
- AAudioEndpointManager &endpointManager = AAudioEndpointManager::getInstance();
- endpointManager.closeEndpoint(endpoint);
-
- // AAudioService::closeStream() prevents two threads from closing at the same time.
- mServiceEndpoint.clear(); // endpoint will hold the pointer after this method returns.
- }
-
- setState(AAUDIO_STREAM_STATE_CLOSED);
-
- mediametrics::LogItem(mMetricsId)
- .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CLOSE)
- .record();
- return result;
+ return closeAndClear();
}
aaudio_result_t AAudioServiceStreamBase::startDevice() {
@@ -250,9 +233,7 @@
* An AAUDIO_SERVICE_EVENT_STARTED will be sent to the client when complete.
*/
aaudio_result_t AAudioServiceStreamBase::start() {
- auto command = std::make_shared<AAudioCommand>(
- START, nullptr, true /*waitForReply*/, TIMEOUT_NANOS);
- return mCommandQueue.sendCommand(command);
+ return sendCommand(START, nullptr, true /*waitForReply*/, TIMEOUT_NANOS);
}
aaudio_result_t AAudioServiceStreamBase::start_l() {
@@ -300,9 +281,7 @@
}
aaudio_result_t AAudioServiceStreamBase::pause() {
- auto command = std::make_shared<AAudioCommand>(
- PAUSE, nullptr, true /*waitForReply*/, TIMEOUT_NANOS);
- return mCommandQueue.sendCommand(command);
+ return sendCommand(PAUSE, nullptr, true /*waitForReply*/, TIMEOUT_NANOS);
}
aaudio_result_t AAudioServiceStreamBase::pause_l() {
@@ -338,9 +317,7 @@
}
aaudio_result_t AAudioServiceStreamBase::stop() {
- auto command = std::make_shared<AAudioCommand>(
- STOP, nullptr, true /*waitForReply*/, TIMEOUT_NANOS);
- return mCommandQueue.sendCommand(command);
+ return sendCommand(STOP, nullptr, true /*waitForReply*/, TIMEOUT_NANOS);
}
aaudio_result_t AAudioServiceStreamBase::stop_l() {
@@ -385,9 +362,7 @@
}
aaudio_result_t AAudioServiceStreamBase::flush() {
- auto command = std::make_shared<AAudioCommand>(
- FLUSH, nullptr, true /*waitForReply*/, TIMEOUT_NANOS);
- return mCommandQueue.sendCommand(command);
+ return sendCommand(FLUSH, nullptr, true /*waitForReply*/, TIMEOUT_NANOS);
}
aaudio_result_t AAudioServiceStreamBase::flush_l() {
@@ -514,8 +489,7 @@
}
void AAudioServiceStreamBase::disconnect() {
- auto command = std::make_shared<AAudioCommand>(DISCONNECT);
- mCommandQueue.sendCommand(command);
+ sendCommand(DISCONNECT);
}
void AAudioServiceStreamBase::disconnect_l() {
@@ -533,12 +507,10 @@
aaudio_result_t AAudioServiceStreamBase::registerAudioThread(pid_t clientThreadId, int priority) {
const pid_t ownerPid = IPCThreadState::self()->getCallingPid(); // TODO review
- auto command = std::make_shared<AAudioCommand>(
- REGISTER_AUDIO_THREAD,
+ return sendCommand(REGISTER_AUDIO_THREAD,
std::make_shared<RegisterAudioThreadParam>(ownerPid, clientThreadId, priority),
true /*waitForReply*/,
TIMEOUT_NANOS);
- return mCommandQueue.sendCommand(command);
}
aaudio_result_t AAudioServiceStreamBase::registerAudioThread_l(
@@ -561,12 +533,10 @@
}
aaudio_result_t AAudioServiceStreamBase::unregisterAudioThread(pid_t clientThreadId) {
- auto command = std::make_shared<AAudioCommand>(
- UNREGISTER_AUDIO_THREAD,
+ return sendCommand(UNREGISTER_AUDIO_THREAD,
std::make_shared<UnregisterAudioThreadParam>(clientThreadId),
true /*waitForReply*/,
TIMEOUT_NANOS);
- return mCommandQueue.sendCommand(command);
}
aaudio_result_t AAudioServiceStreamBase::unregisterAudioThread_l(pid_t clientThreadId) {
@@ -682,12 +652,11 @@
* used to communicate with the underlying HAL or Service.
*/
aaudio_result_t AAudioServiceStreamBase::getDescription(AudioEndpointParcelable &parcelable) {
- auto command = std::make_shared<AAudioCommand>(
+ return sendCommand(
GET_DESCRIPTION,
std::make_shared<GetDescriptionParam>(&parcelable),
true /*waitForReply*/,
TIMEOUT_NANOS);
- return mCommandQueue.sendCommand(command);
}
aaudio_result_t AAudioServiceStreamBase::getDescription_l(AudioEndpointParcelable* parcelable) {
@@ -707,3 +676,33 @@
void AAudioServiceStreamBase::onVolumeChanged(float volume) {
sendServiceEvent(AAUDIO_SERVICE_EVENT_VOLUME, volume);
}
+
+aaudio_result_t AAudioServiceStreamBase::sendCommand(aaudio_command_opcode opCode,
+ std::shared_ptr<AAudioCommandParam> param,
+ bool waitForReply,
+ int64_t timeoutNanos) {
+ return mCommandQueue.sendCommand(std::make_shared<AAudioCommand>(
+ opCode, param, waitForReply, timeoutNanos));
+}
+
+aaudio_result_t AAudioServiceStreamBase::closeAndClear() {
+ aaudio_result_t result = AAUDIO_OK;
+ sp<AAudioServiceEndpoint> endpoint = mServiceEndpointWeak.promote();
+ if (endpoint == nullptr) {
+ result = AAUDIO_ERROR_INVALID_STATE;
+ } else {
+ endpoint->unregisterStream(this);
+ AAudioEndpointManager &endpointManager = AAudioEndpointManager::getInstance();
+ endpointManager.closeEndpoint(endpoint);
+
+ // AAudioService::closeStream() prevents two threads from closing at the same time.
+ mServiceEndpoint.clear(); // endpoint will hold the pointer after this method returns.
+ }
+
+ setState(AAUDIO_STREAM_STATE_CLOSED);
+
+ mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CLOSE)
+ .record();
+ return result;
+}
diff --git a/services/oboeservice/AAudioServiceStreamBase.h b/services/oboeservice/AAudioServiceStreamBase.h
index aa8e8cf..dddd69f 100644
--- a/services/oboeservice/AAudioServiceStreamBase.h
+++ b/services/oboeservice/AAudioServiceStreamBase.h
@@ -366,6 +366,13 @@
aaudio_result_t sendServiceEvent(aaudio_service_event_t event,
double dataDouble);
+ aaudio_result_t sendCommand(aaudio_command_opcode opCode,
+ std::shared_ptr<AAudioCommandParam> param = nullptr,
+ bool waitForReply = false,
+ int64_t timeoutNanos = 0);
+
+ aaudio_result_t closeAndClear();
+
/**
* @return true if the queue is getting full.
*/
diff --git a/services/oboeservice/AAudioServiceStreamMMAP.cpp b/services/oboeservice/AAudioServiceStreamMMAP.cpp
index 05b7f7d..ffc16ac 100644
--- a/services/oboeservice/AAudioServiceStreamMMAP.cpp
+++ b/services/oboeservice/AAudioServiceStreamMMAP.cpp
@@ -167,7 +167,6 @@
// If it fails, get timestamp that was written by getFreeRunningPosition()
aaudio_result_t AAudioServiceStreamMMAP::getHardwareTimestamp_l(int64_t *positionFrames,
int64_t *timeNanos) {
-
sp<AAudioServiceEndpoint> endpoint = mServiceEndpointWeak.promote();
if (endpoint == nullptr) {
ALOGE("%s() has no endpoint", __func__);
@@ -176,17 +175,17 @@
sp<AAudioServiceEndpointMMAP> serviceEndpointMMAP =
static_cast<AAudioServiceEndpointMMAP *>(endpoint.get());
- // Disable this code temporarily because the HAL is not returning
- // a useful result.
-#if 0
uint64_t position;
- if (serviceEndpointMMAP->getExternalPosition(&position, timeNanos) == AAUDIO_OK) {
- ALOGD("%s() getExternalPosition() says pos = %" PRIi64 ", time = %" PRIi64,
+ aaudio_result_t result = serviceEndpointMMAP->getExternalPosition(&position, timeNanos);
+ if (result == AAUDIO_OK) {
+ ALOGV("%s() getExternalPosition() says pos = %" PRIi64 ", time = %" PRIi64,
__func__, position, *timeNanos);
*positionFrames = (int64_t) position;
return AAUDIO_OK;
- } else
-#endif
+ } else {
+ ALOGV("%s() getExternalPosition() returns error %d", __func__, result);
+ }
+
if (mAtomicStreamTimestamp.isValid()) {
Timestamp timestamp = mAtomicStreamTimestamp.read();
*positionFrames = timestamp.getPosition();