Merge "media: Add avic_OWNERS" am: 8db7b98158 am: b58ec7163b am: 736b990ac2 am: 070e306e8e
Original change: https://android-review.googlesource.com/c/platform/frameworks/av/+/2215506
Change-Id: Iebaec49c28c2e41e43a5a8ce3d0326bb79e3795a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/camera/aidl/android/hardware/ICameraServiceProxy.aidl b/camera/aidl/android/hardware/ICameraServiceProxy.aidl
index 88783fb..fefea13 100644
--- a/camera/aidl/android/hardware/ICameraServiceProxy.aidl
+++ b/camera/aidl/android/hardware/ICameraServiceProxy.aidl
@@ -48,5 +48,5 @@
/**
* Checks if the camera has been disabled via device policy.
*/
- boolean isCameraDisabled();
+ boolean isCameraDisabled(int userId);
}
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 35205e5..a22ec19 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -41,8 +41,51 @@
{
"path": "frameworks/av/drm/mediadrm/plugins"
}
+ ],
+
+ "platinum-postsubmit": [
+ // runs regularly, independent of changes in this tree.
+ // signals if changes elsewhere break media functionality
+ // @FlakyTest: in staged-postsubmit, but not postsubmit
+ {
+ "name": "CtsMediaCodecTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.codec.cts.EncodeDecodeTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsMediaCodecTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.codec.cts.DecodeEditEncodeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+
+ "staged-platinum-postsubmit": [
+ // runs every four hours
+ {
+ "name": "CtsMediaCodecTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.codec.cts.EncodeDecodeTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsMediaCodecTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.codec.cts.DecodeEditEncodeTest"
+ }
+ ]
+ }
]
- // TODO (b/229286407) Add EncodeDecodeTest and DecodeEditEncodeTest to
- // platinum-postsubmit once issues in cuttlefish are fixed
}
diff --git a/media/codec2/sfplugin/CCodec.cpp b/media/codec2/sfplugin/CCodec.cpp
index 296d7ed..124a893 100644
--- a/media/codec2/sfplugin/CCodec.cpp
+++ b/media/codec2/sfplugin/CCodec.cpp
@@ -1899,6 +1899,7 @@
comp = state->comp;
}
status_t err = comp->stop();
+ mChannel->stopUseOutputSurface();
if (err != C2_OK) {
// TODO: convert err into status_t
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
@@ -1972,6 +1973,7 @@
}
mChannel->reset();
+ mChannel->stopUseOutputSurface();
// thiz holds strong ref to this while the thread is running.
sp<CCodec> thiz(this);
std::thread([thiz, sendCallback] { thiz->release(sendCallback); }).detach();
@@ -2139,7 +2141,8 @@
std::map<size_t, sp<MediaCodecBuffer>> clientInputBuffers;
status_t err = mChannel->prepareInitialInputBuffers(&clientInputBuffers);
- if (err != OK) {
+ // FIXME(b/237656746)
+ if (err != OK && err != NO_MEMORY) {
ALOGE("Resume request for Input Buffers failed");
mCallback->onError(err, ACTION_CODE_FATAL);
return;
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index 3ca263f..4bf8dce 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -1578,6 +1578,17 @@
mFirstValidFrameIndex = mFrameIndex.load(std::memory_order_relaxed);
}
+void CCodecBufferChannel::stopUseOutputSurface() {
+ if (mOutputSurface.lock()->surface) {
+ C2BlockPool::local_id_t outputPoolId;
+ {
+ Mutexed<BlockPools>::Locked pools(mBlockPools);
+ outputPoolId = pools->outputPoolId;
+ }
+ if (mComponent) mComponent->stopUsingOutputSurface(outputPoolId);
+ }
+}
+
void CCodecBufferChannel::reset() {
stop();
if (mInputSurface != nullptr) {
@@ -1593,14 +1604,6 @@
Mutexed<Output>::Locked output(mOutput);
output->buffers.reset();
}
- if (mOutputSurface.lock()->surface) {
- C2BlockPool::local_id_t outputPoolId;
- {
- Mutexed<BlockPools>::Locked pools(mBlockPools);
- outputPoolId = pools->outputPoolId;
- }
- mComponent->stopUsingOutputSurface(outputPoolId);
- }
}
void CCodecBufferChannel::release() {
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.h b/media/codec2/sfplugin/CCodecBufferChannel.h
index f29a225..61fb06f 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.h
+++ b/media/codec2/sfplugin/CCodecBufferChannel.h
@@ -149,6 +149,12 @@
std::map<size_t, sp<MediaCodecBuffer>> &&clientInputBuffers);
/**
+ * Stop using buffers of the current output surface for other Codec
+ * instances to use the surface safely.
+ */
+ void stopUseOutputSurface();
+
+ /**
* Stop queueing buffers to the component. This object should never queue
* buffers after this call, until start() is called.
*/
diff --git a/media/libaudioclient/AudioEffect.cpp b/media/libaudioclient/AudioEffect.cpp
index 641c4d9..2870c4c 100644
--- a/media/libaudioclient/AudioEffect.cpp
+++ b/media/libaudioclient/AudioEffect.cpp
@@ -568,7 +568,6 @@
if (cb != nullptr) {
cb->onError(mStatus);
}
- mIEffect.clear();
}
// -------------------------------------------------------------------------
diff --git a/media/libaudiohal/impl/EffectsFactoryHalHidl.cpp b/media/libaudiohal/impl/EffectsFactoryHalHidl.cpp
index d7217fc..3470380 100644
--- a/media/libaudiohal/impl/EffectsFactoryHalHidl.cpp
+++ b/media/libaudiohal/impl/EffectsFactoryHalHidl.cpp
@@ -17,6 +17,9 @@
#define LOG_TAG "EffectsFactoryHalHidl"
//#define LOG_NDEBUG 0
+#include <optional>
+#include <tuple>
+
#include <cutils/native_handle.h>
#include <UuidUtils.h>
@@ -38,51 +41,71 @@
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::effect::CPP_VERSION;
+class EffectDescriptorCache {
+ public:
+ using QueryResult = std::tuple<Return<void>, Result, hidl_vec<EffectDescriptor>>;
+ QueryResult queryAllDescriptors(IEffectsFactory* effectsFactory);
+ private:
+ std::mutex mLock;
+ std::optional<hidl_vec<EffectDescriptor>> mLastDescriptors; // GUARDED_BY(mLock)
+};
+
+EffectDescriptorCache::QueryResult EffectDescriptorCache::queryAllDescriptors(
+ IEffectsFactory* effectsFactory) {
+ {
+ std::lock_guard l(mLock);
+ if (mLastDescriptors.has_value()) {
+ return {::android::hardware::Void(), Result::OK, mLastDescriptors.value()};
+ }
+ }
+ Result retval = Result::NOT_INITIALIZED;
+ hidl_vec<EffectDescriptor> descriptors;
+ Return<void> ret = effectsFactory->getAllDescriptors(
+ [&](Result r, const hidl_vec<EffectDescriptor>& result) {
+ retval = r;
+ if (retval == Result::OK) {
+ descriptors = result;
+ }
+ });
+ if (ret.isOk() && retval == Result::OK) {
+ std::lock_guard l(mLock);
+ mLastDescriptors = descriptors;
+ }
+ return {std::move(ret), retval, std::move(descriptors)};
+}
+
EffectsFactoryHalHidl::EffectsFactoryHalHidl(sp<IEffectsFactory> effectsFactory)
- : EffectConversionHelperHidl("EffectsFactory") {
+ : EffectConversionHelperHidl("EffectsFactory"), mCache(new EffectDescriptorCache) {
ALOG_ASSERT(effectsFactory != nullptr, "Provided IEffectsFactory service is NULL");
mEffectsFactory = effectsFactory;
}
-status_t EffectsFactoryHalHidl::queryAllDescriptors() {
- if (mEffectsFactory == 0) return NO_INIT;
- Result retval = Result::NOT_INITIALIZED;
- Return<void> ret = mEffectsFactory->getAllDescriptors(
- [&](Result r, const hidl_vec<EffectDescriptor>& result) {
- retval = r;
- if (retval == Result::OK) {
- mLastDescriptors = result;
- }
- });
- if (ret.isOk()) {
- return retval == Result::OK ? OK : NO_INIT;
- }
- mLastDescriptors.resize(0);
- return processReturn(__FUNCTION__, ret);
-}
-
status_t EffectsFactoryHalHidl::queryNumberEffects(uint32_t *pNumEffects) {
- status_t queryResult = queryAllDescriptors();
- if (queryResult == OK) {
- *pNumEffects = mLastDescriptors.size();
+ if (mEffectsFactory == 0) return NO_INIT;
+ auto [ret, retval, descriptors] = mCache->queryAllDescriptors(mEffectsFactory.get());
+ if (ret.isOk() && retval == Result::OK) {
+ *pNumEffects = descriptors.size();
+ return OK;
+ } else if (ret.isOk()) {
+ return NO_INIT;
}
- return queryResult;
+ return processReturn(__FUNCTION__, ret);
}
status_t EffectsFactoryHalHidl::getDescriptor(
uint32_t index, effect_descriptor_t *pDescriptor) {
- // TODO: We need somehow to track the changes on the server side
- // or figure out how to convert everybody to query all the descriptors at once.
if (pDescriptor == nullptr) {
return BAD_VALUE;
}
- if (mLastDescriptors.size() == 0) {
- status_t queryResult = queryAllDescriptors();
- if (queryResult != OK) return queryResult;
+ if (mEffectsFactory == 0) return NO_INIT;
+ auto [ret, retval, descriptors] = mCache->queryAllDescriptors(mEffectsFactory.get());
+ if (ret.isOk() && retval == Result::OK) {
+ if (index >= descriptors.size()) return NAME_NOT_FOUND;
+ EffectUtils::effectDescriptorToHal(descriptors[index], pDescriptor);
+ } else if (ret.isOk()) {
+ return NO_INIT;
}
- if (index >= mLastDescriptors.size()) return NAME_NOT_FOUND;
- EffectUtils::effectDescriptorToHal(mLastDescriptors[index], pDescriptor);
- return OK;
+ return processReturn(__FUNCTION__, ret);
}
status_t EffectsFactoryHalHidl::getDescriptor(
@@ -114,21 +137,15 @@
if (pEffectType == nullptr || descriptors == nullptr) {
return BAD_VALUE;
}
+ if (mEffectsFactory == 0) return NO_INIT;
- uint32_t numEffects = 0;
- status_t status = queryNumberEffects(&numEffects);
- if (status != NO_ERROR) {
- ALOGW("%s error %d from FactoryHal queryNumberEffects", __func__, status);
- return status;
+ auto [ret, retval, hidlDescs] = mCache->queryAllDescriptors(mEffectsFactory.get());
+ if (!ret.isOk() || retval != Result::OK) {
+ return processReturn(__FUNCTION__, ret, retval);
}
-
- for (uint32_t i = 0; i < numEffects; i++) {
+ for (const auto& hidlDesc : hidlDescs) {
effect_descriptor_t descriptor;
- status = getDescriptor(i, &descriptor);
- if (status != NO_ERROR) {
- ALOGW("%s error %d from FactoryHal getDescriptor", __func__, status);
- continue;
- }
+ EffectUtils::effectDescriptorToHal(hidlDesc, &descriptor);
if (memcmp(&descriptor.type, pEffectType, sizeof(effect_uuid_t)) == 0) {
descriptors->push_back(descriptor);
}
diff --git a/media/libaudiohal/impl/EffectsFactoryHalHidl.h b/media/libaudiohal/impl/EffectsFactoryHalHidl.h
index e1882e1..7ccddc6 100644
--- a/media/libaudiohal/impl/EffectsFactoryHalHidl.h
+++ b/media/libaudiohal/impl/EffectsFactoryHalHidl.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
#define ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
+#include <memory>
+
#include PATH(android/hardware/audio/effect/FILE_VERSION/IEffectsFactory.h)
#include <media/audiohal/EffectsFactoryHalInterface.h>
@@ -28,6 +30,8 @@
using ::android::hardware::hidl_vec;
using namespace ::android::hardware::audio::effect::CPP_VERSION;
+class EffectDescriptorCache;
+
class EffectsFactoryHalHidl : public EffectsFactoryHalInterface, public EffectConversionHelperHidl
{
public:
@@ -63,9 +67,7 @@
private:
sp<IEffectsFactory> mEffectsFactory;
- hidl_vec<EffectDescriptor> mLastDescriptors;
-
- status_t queryAllDescriptors();
+ std::unique_ptr<EffectDescriptorCache> mCache;
};
} // namespace effect
diff --git a/media/libheadtracking/HeadTrackingProcessor.cpp b/media/libheadtracking/HeadTrackingProcessor.cpp
index 2fc9ff9..101b825 100644
--- a/media/libheadtracking/HeadTrackingProcessor.cpp
+++ b/media/libheadtracking/HeadTrackingProcessor.cpp
@@ -88,10 +88,12 @@
}
void calculate(int64_t timestamp) override {
- // Handle the screen first, since it might trigger a recentering of the head.
+ bool screenStable = true;
+
+ // Handle the screen first, since it might: trigger a recentering of the head.
if (mWorldToScreenTimestamp.has_value()) {
const Pose3f worldToLogicalScreen = mScreenPoseBias.getOutput();
- bool screenStable = mScreenStillnessDetector.calculate(timestamp);
+ screenStable = mScreenStillnessDetector.calculate(timestamp);
mModeSelector.setScreenStable(mWorldToScreenTimestamp.value(), screenStable);
// Whenever the screen is unstable, recenter the head pose.
if (!screenStable) {
@@ -105,7 +107,8 @@
if (mWorldToHeadTimestamp.has_value()) {
Pose3f worldToHead = mHeadPoseBias.getOutput();
// Auto-recenter.
- if (mHeadStillnessDetector.calculate(timestamp)) {
+ bool headStable = mHeadStillnessDetector.calculate(timestamp);
+ if (headStable || !screenStable) {
recenter(true, false);
worldToHead = mHeadPoseBias.getOutput();
}
diff --git a/media/libheadtracking/StillnessDetector.cpp b/media/libheadtracking/StillnessDetector.cpp
index be7c893..5232084 100644
--- a/media/libheadtracking/StillnessDetector.cpp
+++ b/media/libheadtracking/StillnessDetector.cpp
@@ -26,6 +26,9 @@
mFifo.clear();
mWindowFull = false;
mSuppressionDeadline.reset();
+ // A "true" state indicates stillness is detected (default = true)
+ mCurrentState = true;
+ mPreviousState = true;
}
void StillnessDetector::setInput(int64_t timestamp, const Pose3f& input) {
@@ -33,7 +36,15 @@
discardOld(timestamp);
}
+bool StillnessDetector::getPreviousState() const {
+ return mPreviousState;
+}
+
bool StillnessDetector::calculate(int64_t timestamp) {
+ // Move the current stillness state to the previous state.
+ // This allows us to detect transitions into and out of stillness.
+ mPreviousState = mCurrentState;
+
discardOld(timestamp);
// Check whether all the poses in the queue are in the proximity of the new one. We want to do
@@ -60,15 +71,17 @@
// If the window has not been full, return the default value.
if (!mWindowFull) {
- return mOptions.defaultValue;
+ mCurrentState = mOptions.defaultValue;
}
-
// Force "in motion" while the suppression deadline is active.
- if (mSuppressionDeadline.has_value()) {
- return false;
+ else if (mSuppressionDeadline.has_value()) {
+ mCurrentState = false;
+ }
+ else {
+ mCurrentState = !moved;
}
- return !moved;
+ return mCurrentState;
}
void StillnessDetector::discardOld(int64_t timestamp) {
diff --git a/media/libheadtracking/StillnessDetector.h b/media/libheadtracking/StillnessDetector.h
index ee4b2d8..abd7cc4 100644
--- a/media/libheadtracking/StillnessDetector.h
+++ b/media/libheadtracking/StillnessDetector.h
@@ -80,7 +80,8 @@
void setInput(int64_t timestamp, const Pose3f& input);
/** Calculate whether the stream is still at the given timestamp. */
bool calculate(int64_t timestamp);
-
+ /** Return the stillness state from the previous call to calculate() */
+ bool getPreviousState() const;
private:
struct TimestampedPose {
int64_t timestamp;
@@ -92,6 +93,8 @@
const float mCosHalfRotationalThreshold;
std::deque<TimestampedPose> mFifo;
bool mWindowFull = false;
+ bool mCurrentState = true;
+ bool mPreviousState = true;
// As soon as motion is detected, this will be set for the time of detection + window duration,
// and during this time we will always consider outselves in motion without checking. This is
// used for hyteresis purposes, since because of the approximate method we use for determining
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index ea1fdf4..a0bc8ca 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -1966,6 +1966,10 @@
format->setString("mime", MEDIA_MIMETYPE_VIDEO_HEVC);
break;
+ case VIDEO_ENCODER_DOLBY_VISION:
+ format->setString("mime", MEDIA_MIMETYPE_VIDEO_DOLBY_VISION);
+ break;
+
default:
CHECK(!"Should not be here, unsupported video encoding.");
break;
diff --git a/media/libmediaplayerservice/nuplayer/AWakeLock.cpp b/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
index c3bd207..25a8ae4 100644
--- a/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
+++ b/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
@@ -59,11 +59,10 @@
if (mPowerManager != NULL) {
sp<IBinder> binder = new BBinder();
int64_t token = IPCThreadState::self()->clearCallingIdentity();
- binder::Status status = mPowerManager->acquireWakeLock(
+ binder::Status status = mPowerManager->acquireWakeLockAsync(
binder, POWERMANAGER_PARTIAL_WAKE_LOCK,
String16("AWakeLock"), String16("media"),
- {} /* workSource */, {} /* historyTag */, -1 /* displayId */,
- nullptr /* callback */);
+ {} /* workSource */, {} /* historyTag */);
IPCThreadState::self()->restoreCallingIdentity(token);
if (status.isOk()) {
mWakeLockToken = binder;
diff --git a/media/libstagefright/HevcUtils.cpp b/media/libstagefright/HevcUtils.cpp
index 5f9c20e..60df162 100644
--- a/media/libstagefright/HevcUtils.cpp
+++ b/media/libstagefright/HevcUtils.cpp
@@ -102,10 +102,11 @@
static bool findParam(uint32_t key, T *param,
KeyedVector<uint32_t, uint64_t> ¶ms) {
CHECK(param);
- if (params.indexOfKey(key) < 0) {
+ ssize_t index = params.indexOfKey(key);
+ if (index < 0) {
return false;
}
- *param = (T) params[key];
+ *param = (T) params[index];
return true;
}
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index e50880a..c963e19 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -3534,6 +3534,17 @@
}
setState(STARTED);
postPendingRepliesAndDeferredMessages("kWhatStartCompleted");
+
+ // Now that the codec has started, configure, by default, the peek behavior to
+ // be undefined for backwards compatibility with older releases. Later, if an
+ // app explicitly enables or disables peek, the parameter will be turned off and
+ // the legacy undefined behavior is disallowed.
+ // See updateTunnelPeek called in onSetParameters for more details.
+ if (mTunneled && mTunnelPeekState == TunnelPeekState::kLegacyMode) {
+ sp<AMessage> params = new AMessage;
+ params->setInt32("android._tunnel-peek-set-legacy", 1);
+ mCodec->signalSetParameters(params);
+ }
break;
}
@@ -3973,14 +3984,6 @@
mTunneled = false;
}
- // If mTunnelPeekState is still in kLegacyMode at this point,
- // configure the codec in legacy mode
- if (mTunneled && (mTunnelPeekState == TunnelPeekState::kLegacyMode)) {
- sp<AMessage> params = new AMessage;
- params->setInt32("android._tunnel-peek-set-legacy", 1);
- onSetParameters(params);
- }
-
int32_t background = 0;
if (format->findInt32("android._background-mode", &background) && background) {
androidSetThreadPriority(gettid(), ANDROID_PRIORITY_BACKGROUND);
diff --git a/media/ndk/NdkMediaCodec.cpp b/media/ndk/NdkMediaCodec.cpp
index 38e422d..ed31c02 100644
--- a/media/ndk/NdkMediaCodec.cpp
+++ b/media/ndk/NdkMediaCodec.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <charconv>
#include <inttypes.h>
#include <mutex>
#include <set>
@@ -324,29 +325,61 @@
}
AMessage::Type type;
- int64_t mediaTimeUs, systemNano;
- size_t index = 0;
+ size_t n = data->countEntries();
- // TODO. This code has dependency with MediaCodec::CreateFramesRenderedMessage.
- for (size_t ix = 0; ix < data->countEntries(); ix++) {
- AString name = data->getEntryNameAt(ix, &type);
- if (name.startsWith(AStringPrintf("%zu-media-time-us", index).c_str())) {
- AMessage::ItemData data = msg->getEntryAt(index);
- data.find(&mediaTimeUs);
- } else if (name.startsWith(AStringPrintf("%zu-system-nano", index).c_str())) {
- AMessage::ItemData data = msg->getEntryAt(index);
- data.find(&systemNano);
+ thread_local std::vector<std::optional<int64_t>> mediaTimesInUs;
+ thread_local std::vector<std::optional<int64_t>> systemTimesInNs;
+ mediaTimesInUs.resize(n);
+ systemTimesInNs.resize(n);
+ std::fill_n(mediaTimesInUs.begin(), n, std::nullopt);
+ std::fill_n(systemTimesInNs.begin(), n, std::nullopt);
+ for (size_t i = 0; i < n; i++) {
+ AString name = data->getEntryNameAt(i, &type);
+ if (name.endsWith("-media-time-us")) {
+ int64_t mediaTimeUs;
+ AMessage::ItemData itemData = data->getEntryAt(i);
+ itemData.find(&mediaTimeUs);
- Mutex::Autolock _l(mCodec->mFrameRenderedCallbackLock);
- if (mCodec->mFrameRenderedCallback != NULL) {
+ int index = -1;
+ std::from_chars_result result = std::from_chars(
+ name.c_str(), name.c_str() + name.find("-"), index);
+ if (result.ec == std::errc() && 0 <= index && index < n) {
+ mediaTimesInUs[index] = mediaTimeUs;
+ } else {
+ std::error_code ec = std::make_error_code(result.ec);
+ ALOGE("Unexpected media time index: #%d with value %lldus (err=%d %s)",
+ index, (long long)mediaTimeUs, ec.value(), ec.message().c_str());
+ }
+ } else if (name.endsWith("-system-nano")) {
+ int64_t systemNano;
+ AMessage::ItemData itemData = data->getEntryAt(i);
+ itemData.find(&systemNano);
+
+ int index = -1;
+ std::from_chars_result result = std::from_chars(
+ name.c_str(), name.c_str() + name.find("-"), index);
+ if (result.ec == std::errc() && 0 <= index && index < n) {
+ systemTimesInNs[index] = systemNano;
+ } else {
+ std::error_code ec = std::make_error_code(result.ec);
+ ALOGE("Unexpected system time index: #%d with value %lldns (err=%d %s)",
+ index, (long long)systemNano, ec.value(), ec.message().c_str());
+ }
+ }
+ }
+
+ Mutex::Autolock _l(mCodec->mFrameRenderedCallbackLock);
+ if (mCodec->mFrameRenderedCallback != NULL) {
+ for (size_t i = 0; i < n; ++i) {
+ if (mediaTimesInUs[i] && systemTimesInNs[i]) {
mCodec->mFrameRenderedCallback(
mCodec,
mCodec->mFrameRenderedCallbackUserData,
- mediaTimeUs,
- systemNano);
+ mediaTimesInUs[i].value(),
+ systemTimesInNs[i].value());
+ } else {
+ break;
}
-
- index++;
}
}
break;
@@ -529,22 +562,31 @@
AMediaCodecOnAsyncNotifyCallback callback,
void *userdata) {
- Mutex::Autolock _l(mData->mAsyncCallbackLock);
-
- if (mData->mAsyncNotify == NULL) {
- mData->mAsyncNotify = new AMessage(kWhatAsyncNotify, mData->mHandler);
+ {
+ Mutex::Autolock _l(mData->mAsyncCallbackLock);
+ if (mData->mAsyncNotify == NULL) {
+ mData->mAsyncNotify = new AMessage(kWhatAsyncNotify, mData->mHandler);
+ }
+ // we set this ahead so that we can be ready
+ // to receive callbacks as soon as the next call is a
+ // success.
+ mData->mAsyncCallback = callback;
+ mData->mAsyncCallbackUserData = userdata;
}
// always call, codec may have been reset/re-configured since last call.
status_t err = mData->mCodec->setCallback(mData->mAsyncNotify);
if (err != OK) {
+ {
+ //The setup gone wrong. clean up the pointers.
+ Mutex::Autolock _l(mData->mAsyncCallbackLock);
+ mData->mAsyncCallback = {};
+ mData->mAsyncCallbackUserData = nullptr;
+ }
ALOGE("setAsyncNotifyCallback: err(%d), failed to set async callback", err);
return translate_error(err);
}
- mData->mAsyncCallback = callback;
- mData->mAsyncCallbackUserData = userdata;
-
return AMEDIA_OK;
}
diff --git a/media/utils/MethodStatistics.cpp b/media/utils/MethodStatistics.cpp
index b179b20..086757b 100644
--- a/media/utils/MethodStatistics.cpp
+++ b/media/utils/MethodStatistics.cpp
@@ -22,7 +22,8 @@
std::shared_ptr<std::vector<std::string>>
getStatisticsClassesForModule(std::string_view moduleName) {
- static const std::map<std::string, std::shared_ptr<std::vector<std::string>>> m {
+ static const std::map<std::string, std::shared_ptr<std::vector<std::string>>,
+ std::less<> /* transparent comparator */> m {
{
METHOD_STATISTICS_MODULE_NAME_AUDIO_HIDL,
std::shared_ptr<std::vector<std::string>>(
@@ -34,13 +35,14 @@
})
},
};
- auto it = m.find({moduleName.begin(), moduleName.end()});
+ auto it = m.find(moduleName);
if (it == m.end()) return {};
return it->second;
}
static void addClassesToMap(const std::shared_ptr<std::vector<std::string>> &classNames,
- std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>> &map) {
+ std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>,
+ std::less<> /* transparent comparator */> &map) {
if (classNames) {
for (const auto& className : *classNames) {
map.emplace(className, std::make_shared<MethodStatistics<std::string>>());
@@ -51,17 +53,18 @@
// singleton statistics for DeviceHalHidl StreamOutHalHidl StreamInHalHidl
std::shared_ptr<MethodStatistics<std::string>>
getStatisticsForClass(std::string_view className) {
- static const std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>> m =
+ static const std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>,
+ std::less<> /* transparent comparator */> m =
// copy elided initialization of map m.
[](){
- std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>> m;
+ std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>, std::less<>> m;
addClassesToMap(
getStatisticsClassesForModule(METHOD_STATISTICS_MODULE_NAME_AUDIO_HIDL),
m);
return m;
}();
- auto it = m.find({className.begin(), className.end()});
+ auto it = m.find(className);
if (it == m.end()) return {};
return it->second;
}
diff --git a/media/utils/TimeCheck.cpp b/media/utils/TimeCheck.cpp
index 0848eb3..6823f4f 100644
--- a/media/utils/TimeCheck.cpp
+++ b/media/utils/TimeCheck.cpp
@@ -21,6 +21,7 @@
#include <android-base/logging.h>
#include <audio_utils/clock.h>
#include <mediautils/EventLog.h>
+#include <mediautils/FixedString.h>
#include <mediautils/MethodStatistics.h>
#include <mediautils/TimeCheck.h>
#include <utils/Log.h>
@@ -138,22 +139,24 @@
return getTimeCheckThread().toString();
}
-TimeCheck::TimeCheck(std::string tag, OnTimerFunc&& onTimer, uint32_t timeoutMs,
- bool crashOnTimeout)
- : mTimeCheckHandler(new TimeCheckHandler{
- std::move(tag), std::move(onTimer), crashOnTimeout,
- std::chrono::system_clock::now(), gettid()})
- , mTimerHandle(timeoutMs == 0
+TimeCheck::TimeCheck(std::string_view tag, OnTimerFunc&& onTimer, Duration requestedTimeoutDuration,
+ Duration secondChanceDuration, bool crashOnTimeout)
+ : mTimeCheckHandler{ std::make_shared<TimeCheckHandler>(
+ tag, std::move(onTimer), crashOnTimeout, requestedTimeoutDuration,
+ secondChanceDuration, std::chrono::system_clock::now(), gettid()) }
+ , mTimerHandle(requestedTimeoutDuration.count() == 0
+ /* for TimeCheck we don't consider a non-zero secondChanceDuration here */
? getTimeCheckThread().trackTask(mTimeCheckHandler->tag)
: getTimeCheckThread().scheduleTask(
mTimeCheckHandler->tag,
// Pass in all the arguments by value to this task for safety.
// The thread could call the callback before the constructor is finished.
// The destructor is not blocked on callback.
- [ timeCheckHandler = mTimeCheckHandler ] {
- timeCheckHandler->onTimeout();
+ [ timeCheckHandler = mTimeCheckHandler ](TimerThread::Handle timerHandle) {
+ timeCheckHandler->onTimeout(timerHandle);
},
- std::chrono::milliseconds(timeoutMs))) {}
+ requestedTimeoutDuration,
+ secondChanceDuration)) {}
TimeCheck::~TimeCheck() {
if (mTimeCheckHandler) {
@@ -161,23 +164,77 @@
}
}
+/* static */
+std::string TimeCheck::analyzeTimeouts(
+ float requestedTimeoutMs, float elapsedSteadyMs, float elapsedSystemMs) {
+ // Track any OS clock issues with suspend.
+ // It is possible that the elapsedSystemMs is much greater than elapsedSteadyMs if
+ // a suspend occurs; however, we always expect the timeout ms should always be slightly
+ // less than the elapsed steady ms regardless of whether a suspend occurs or not.
+
+ std::string s("Timeout ms ");
+ s.append(std::to_string(requestedTimeoutMs))
+ .append(" elapsed steady ms ").append(std::to_string(elapsedSteadyMs))
+ .append(" elapsed system ms ").append(std::to_string(elapsedSystemMs));
+
+ // Is there something unusual?
+ static constexpr float TOLERANCE_CONTEXT_SWITCH_MS = 200.f;
+
+ if (requestedTimeoutMs > elapsedSteadyMs || requestedTimeoutMs > elapsedSystemMs) {
+ s.append("\nError: early expiration - "
+ "requestedTimeoutMs should be less than elapsed time");
+ }
+
+ if (elapsedSteadyMs > elapsedSystemMs + TOLERANCE_CONTEXT_SWITCH_MS) {
+ s.append("\nWarning: steady time should not advance faster than system time");
+ }
+
+ // This has been found in suspend stress testing.
+ if (elapsedSteadyMs > requestedTimeoutMs + TOLERANCE_CONTEXT_SWITCH_MS) {
+ s.append("\nWarning: steady time significantly exceeds timeout "
+ "- possible thread stall or aborted suspend");
+ }
+
+ // This has been found in suspend stress testing.
+ if (elapsedSystemMs > requestedTimeoutMs + TOLERANCE_CONTEXT_SWITCH_MS) {
+ s.append("\nInformation: system time significantly exceeds timeout "
+ "- possible suspend");
+ }
+ return s;
+}
+
+// To avoid any potential race conditions, the timer handle
+// (expiration = clock steady start + timeout) is passed into the callback.
void TimeCheck::TimeCheckHandler::onCancel(TimerThread::Handle timerHandle) const
{
if (TimeCheck::getTimeCheckThread().cancelTask(timerHandle) && onTimer) {
- const std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now();
- onTimer(false /* timeout */,
- std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
- endTime - startTime).count());
+ const std::chrono::steady_clock::time_point endSteadyTime =
+ std::chrono::steady_clock::now();
+ const float elapsedSteadyMs = std::chrono::duration_cast<FloatMs>(
+ endSteadyTime - timerHandle + timeoutDuration).count();
+ // send the elapsed steady time for statistics.
+ onTimer(false /* timeout */, elapsedSteadyMs);
}
}
-void TimeCheck::TimeCheckHandler::onTimeout() const
+// To avoid any potential race conditions, the timer handle
+// (expiration = clock steady start + timeout) is passed into the callback.
+void TimeCheck::TimeCheckHandler::onTimeout(TimerThread::Handle timerHandle) const
{
- const std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now();
+ const std::chrono::steady_clock::time_point endSteadyTime = std::chrono::steady_clock::now();
+ const std::chrono::system_clock::time_point endSystemTime = std::chrono::system_clock::now();
+ // timerHandle incorporates the timeout
+ const float elapsedSteadyMs = std::chrono::duration_cast<FloatMs>(
+ endSteadyTime - (timerHandle - timeoutDuration)).count();
+ const float elapsedSystemMs = std::chrono::duration_cast<FloatMs>(
+ endSystemTime - startSystemTime).count();
+ const float requestedTimeoutMs = std::chrono::duration_cast<FloatMs>(
+ timeoutDuration).count();
+ const float secondChanceMs = std::chrono::duration_cast<FloatMs>(
+ secondChanceDuration).count();
+
if (onTimer) {
- onTimer(true /* timeout */,
- std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
- endTime - startTime).count());
+ onTimer(true /* timeout */, elapsedSteadyMs);
}
if (!crashOnTimeout) return;
@@ -207,8 +264,10 @@
// Create abort message string - caution: this can be very large.
const std::string abortMessage = std::string("TimeCheck timeout for ")
.append(tag)
- .append(" scheduled ").append(formatTime(startTime))
+ .append(" scheduled ").append(formatTime(startSystemTime))
.append(" on thread ").append(std::to_string(tid)).append("\n")
+ .append(analyzeTimeouts(requestedTimeoutMs + secondChanceMs,
+ elapsedSteadyMs, elapsedSystemMs)).append("\n")
.append(halPids).append("\n")
.append(summary);
@@ -231,16 +290,16 @@
mediautils::getStatisticsForClass(className);
if (!statistics) return {}; // empty TimeCheck.
return mediautils::TimeCheck(
- std::string(className).append("::").append(methodName),
- [ clazz = std::string(className), method = std::string(methodName),
+ FixedString62(className).append("::").append(methodName),
+ [ safeMethodName = FixedString30(methodName),
stats = std::move(statistics) ]
(bool timeout, float elapsedMs) {
if (timeout) {
; // ignored, there is no timeout value.
} else {
- stats->event(method, elapsedMs);
+ stats->event(safeMethodName.asStringView(), elapsedMs);
}
- }, 0 /* timeoutMs */);
+ }, {} /* timeoutDuration */, {} /* secondChanceDuration */, false /* crashOnTimeout */);
}
} // namespace android::mediautils
diff --git a/media/utils/TimerThread-test.cpp b/media/utils/TimerThread-test.cpp
index 93cd64c..9452c07 100644
--- a/media/utils/TimerThread-test.cpp
+++ b/media/utils/TimerThread-test.cpp
@@ -33,10 +33,28 @@
return std::count(s.begin(), s.end(), c);
}
-TEST(TimerThread, Basic) {
+
+// Split msec time between timeout and second chance time
+// This tests expiration times weighted between timeout and the second chance time.
+#define DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(msec, frac) \
+ std::chrono::milliseconds(int((msec) * (frac)) + 1), \
+ std::chrono::milliseconds(int((msec) * (1.f - (frac))))
+
+// The TimerThreadTest is parameterized on a fraction between 0.f and 1.f which
+// is how the total timeout time is split between the first timeout and the second chance time.
+//
+class TimerThreadTest : public ::testing::TestWithParam<float> {
+protected:
+
+static void testBasic() {
+ const auto frac = GetParam();
+
std::atomic<bool> taskRan = false;
TimerThread thread;
- thread.scheduleTask("Basic", [&taskRan] { taskRan = true; }, 100ms);
+ TimerThread::Handle handle =
+ thread.scheduleTask("Basic", [&taskRan](TimerThread::Handle handle __unused) {
+ taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
+ ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
std::this_thread::sleep_for(100ms - kJitter);
ASSERT_FALSE(taskRan);
std::this_thread::sleep_for(2 * kJitter);
@@ -44,11 +62,15 @@
ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
}
-TEST(TimerThread, Cancel) {
+static void testCancel() {
+ const auto frac = GetParam();
+
std::atomic<bool> taskRan = false;
TimerThread thread;
TimerThread::Handle handle =
- thread.scheduleTask("Cancel", [&taskRan] { taskRan = true; }, 100ms);
+ thread.scheduleTask("Cancel", [&taskRan](TimerThread::Handle handle __unused) {
+ taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
+ ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
std::this_thread::sleep_for(100ms - kJitter);
ASSERT_FALSE(taskRan);
ASSERT_TRUE(thread.cancelTask(handle));
@@ -57,88 +79,87 @@
ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
}
-TEST(TimerThread, CancelAfterRun) {
+static void testCancelAfterRun() {
+ const auto frac = GetParam();
+
std::atomic<bool> taskRan = false;
TimerThread thread;
TimerThread::Handle handle =
- thread.scheduleTask("CancelAfterRun", [&taskRan] { taskRan = true; }, 100ms);
+ thread.scheduleTask("CancelAfterRun",
+ [&taskRan](TimerThread::Handle handle __unused) {
+ taskRan = true; },
+ DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
+ ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
std::this_thread::sleep_for(100ms + kJitter);
ASSERT_TRUE(taskRan);
ASSERT_FALSE(thread.cancelTask(handle));
ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
}
-TEST(TimerThread, MultipleTasks) {
+static void testMultipleTasks() {
+ const auto frac = GetParam();
+
std::array<std::atomic<bool>, 6> taskRan{};
TimerThread thread;
auto startTime = std::chrono::steady_clock::now();
- thread.scheduleTask("0", [&taskRan] { taskRan[0] = true; }, 300ms);
- thread.scheduleTask("1", [&taskRan] { taskRan[1] = true; }, 100ms);
- thread.scheduleTask("2", [&taskRan] { taskRan[2] = true; }, 200ms);
- thread.scheduleTask("3", [&taskRan] { taskRan[3] = true; }, 400ms);
- auto handle4 = thread.scheduleTask("4", [&taskRan] { taskRan[4] = true; }, 200ms);
- thread.scheduleTask("5", [&taskRan] { taskRan[5] = true; }, 200ms);
+ thread.scheduleTask("0", [&taskRan](TimerThread::Handle handle __unused) {
+ taskRan[0] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(300, frac));
+ thread.scheduleTask("1", [&taskRan](TimerThread::Handle handle __unused) {
+ taskRan[1] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
+ thread.scheduleTask("2", [&taskRan](TimerThread::Handle handle __unused) {
+ taskRan[2] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
+ thread.scheduleTask("3", [&taskRan](TimerThread::Handle handle __unused) {
+ taskRan[3] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(400, frac));
+ auto handle4 = thread.scheduleTask("4", [&taskRan](TimerThread::Handle handle __unused) {
+ taskRan[4] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
+ thread.scheduleTask("5", [&taskRan](TimerThread::Handle handle __unused) {
+ taskRan[5] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
// 6 tasks pending
ASSERT_EQ(6, countChars(thread.pendingToString(), REQUEST_START));
// 0 tasks completed
ASSERT_EQ(0, countChars(thread.retiredToString(), REQUEST_START));
+ // None of the tasks are expected to have finished at the start.
+ std::array<std::atomic<bool>, 6> expected{};
+
// Task 1 should trigger around 100ms.
std::this_thread::sleep_until(startTime + 100ms - kJitter);
- ASSERT_FALSE(taskRan[0]);
- ASSERT_FALSE(taskRan[1]);
- ASSERT_FALSE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_FALSE(taskRan[5]);
+
+ ASSERT_EQ(expected, taskRan);
+
std::this_thread::sleep_until(startTime + 100ms + kJitter);
- ASSERT_FALSE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_FALSE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_FALSE(taskRan[5]);
+
+ expected[1] = true;
+ ASSERT_EQ(expected, taskRan);
// Cancel task 4 before it gets a chance to run.
thread.cancelTask(handle4);
// Tasks 2 and 5 should trigger around 200ms.
std::this_thread::sleep_until(startTime + 200ms - kJitter);
- ASSERT_FALSE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_FALSE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_FALSE(taskRan[5]);
+
+ ASSERT_EQ(expected, taskRan);
+
std::this_thread::sleep_until(startTime + 200ms + kJitter);
- ASSERT_FALSE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_TRUE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_TRUE(taskRan[5]);
+
+ expected[2] = true;
+ expected[5] = true;
+ ASSERT_EQ(expected, taskRan);
// Task 0 should trigger around 300ms.
std::this_thread::sleep_until(startTime + 300ms - kJitter);
- ASSERT_FALSE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_TRUE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_TRUE(taskRan[5]);
+
+ ASSERT_EQ(expected, taskRan);
std::this_thread::sleep_until(startTime + 300ms + kJitter);
- ASSERT_TRUE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_TRUE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_TRUE(taskRan[5]);
+
+ expected[0] = true;
+ ASSERT_EQ(expected, taskRan);
// 1 task pending
ASSERT_EQ(1, countChars(thread.pendingToString(), REQUEST_START));
@@ -147,23 +168,16 @@
// Task 3 should trigger around 400ms.
std::this_thread::sleep_until(startTime + 400ms - kJitter);
- ASSERT_TRUE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_TRUE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_TRUE(taskRan[5]);
+
+ ASSERT_EQ(expected, taskRan);
// 4 tasks ran and 1 cancelled
ASSERT_EQ(4 + 1, countChars(thread.retiredToString(), REQUEST_START));
std::this_thread::sleep_until(startTime + 400ms + kJitter);
- ASSERT_TRUE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_TRUE(taskRan[2]);
- ASSERT_TRUE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_TRUE(taskRan[5]);
+
+ expected[3] = true;
+ ASSERT_EQ(expected, taskRan);
// 0 tasks pending
ASSERT_EQ(0, countChars(thread.pendingToString(), REQUEST_START));
@@ -171,6 +185,30 @@
ASSERT_EQ(5 + 1, countChars(thread.retiredToString(), REQUEST_START));
}
+}; // class TimerThreadTest
+
+TEST_P(TimerThreadTest, Basic) {
+ testBasic();
+}
+
+TEST_P(TimerThreadTest, Cancel) {
+ testCancel();
+}
+
+TEST_P(TimerThreadTest, CancelAfterRun) {
+ testCancelAfterRun();
+}
+
+TEST_P(TimerThreadTest, MultipleTasks) {
+ testMultipleTasks();
+}
+
+INSTANTIATE_TEST_CASE_P(
+ TimerThread,
+ TimerThreadTest,
+ ::testing::Values(0.f, 0.5f, 1.f)
+ );
+
TEST(TimerThread, TrackedTasks) {
TimerThread thread;
@@ -178,6 +216,10 @@
auto handle1 = thread.trackTask("1");
auto handle2 = thread.trackTask("2");
+ ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle0));
+ ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle1));
+ ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle2));
+
// 3 tasks pending
ASSERT_EQ(3, countChars(thread.pendingToString(), REQUEST_START));
// 0 tasks retired
@@ -201,6 +243,7 @@
// Add another tracked task.
auto handle3 = thread.trackTask("3");
+ ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle3));
// 2 tasks pending
ASSERT_EQ(2, countChars(thread.pendingToString(), REQUEST_START));
diff --git a/media/utils/TimerThread.cpp b/media/utils/TimerThread.cpp
index 6de6b13..5e58a3d 100644
--- a/media/utils/TimerThread.cpp
+++ b/media/utils/TimerThread.cpp
@@ -23,30 +23,35 @@
#include <mediautils/MediaUtilsDelayed.h>
#include <mediautils/TimerThread.h>
+#include <utils/Log.h>
#include <utils/ThreadDefs.h>
+using namespace std::chrono_literals;
+
namespace android::mediautils {
extern std::string formatTime(std::chrono::system_clock::time_point t);
extern std::string_view timeSuffix(std::string_view time1, std::string_view time2);
TimerThread::Handle TimerThread::scheduleTask(
- std::string tag, std::function<void()>&& func, std::chrono::milliseconds timeout) {
+ std::string_view tag, TimerCallback&& func,
+ Duration timeoutDuration, Duration secondChanceDuration) {
const auto now = std::chrono::system_clock::now();
- std::shared_ptr<const Request> request{
- new Request{ now, now + timeout, gettid(), std::move(tag) }};
- return mMonitorThread.add(std::move(request), std::move(func), timeout);
+ auto request = std::make_shared<const Request>(now, now +
+ std::chrono::duration_cast<std::chrono::system_clock::duration>(timeoutDuration),
+ secondChanceDuration, gettid(), tag);
+ return mMonitorThread.add(std::move(request), std::move(func), timeoutDuration);
}
-TimerThread::Handle TimerThread::trackTask(std::string tag) {
+TimerThread::Handle TimerThread::trackTask(std::string_view tag) {
const auto now = std::chrono::system_clock::now();
- std::shared_ptr<const Request> request{
- new Request{ now, now, gettid(), std::move(tag) }};
+ auto request = std::make_shared<const Request>(now, now,
+ Duration{} /* secondChanceDuration */, gettid(), tag);
return mNoTimeoutMap.add(std::move(request));
}
bool TimerThread::cancelTask(Handle handle) {
- std::shared_ptr<const Request> request = mNoTimeoutMap.isValidHandle(handle) ?
+ std::shared_ptr<const Request> request = isNoTimeoutHandle(handle) ?
mNoTimeoutMap.remove(handle) : mMonitorThread.remove(handle);
if (!request) return false;
mRetiredQueue.add(std::move(request));
@@ -84,6 +89,8 @@
return std::string("now ")
.append(formatTime(std::chrono::system_clock::now()))
+ .append("\nsecondChanceCount ")
+ .append(std::to_string(mMonitorThread.getSecondChanceCount()))
.append(analysisSummary)
.append("\ntimeout [ ")
.append(requestsToString(timeoutRequests))
@@ -106,10 +113,10 @@
//
/* static */
bool TimerThread::isRequestFromHal(const std::shared_ptr<const Request>& request) {
- const size_t hidlPos = request->tag.find("Hidl");
+ const size_t hidlPos = request->tag.asStringView().find("Hidl");
if (hidlPos == std::string::npos) return false;
// should be a separator afterwards Hidl which indicates the string was in the class.
- const size_t separatorPos = request->tag.find("::", hidlPos);
+ const size_t separatorPos = request->tag.asStringView().find("::", hidlPos);
return separatorPos != std::string::npos;
}
@@ -127,12 +134,12 @@
std::vector<std::shared_ptr<const Request>> pendingExact;
std::vector<std::shared_ptr<const Request>> pendingPossible;
- // We look at pending requests that were scheduled no later than kDuration
+ // We look at pending requests that were scheduled no later than kPendingDuration
// after the timeout request. This prevents false matches with calls
// that naturally block for a short period of time
// such as HAL write() and read().
//
- auto constexpr kDuration = std::chrono::milliseconds(1000);
+ constexpr Duration kPendingDuration = 1000ms;
for (const auto& pending : pendingRequests) {
// If the pending tid is the same as timeout tid, problem identified.
if (pending->tid == timeout->tid) {
@@ -141,7 +148,7 @@
}
// if the pending tid is scheduled within time limit
- if (pending->scheduled - timeout->scheduled < kDuration) {
+ if (pending->scheduled - timeout->scheduled < kPendingDuration) {
pendingPossible.emplace_back(pending);
}
}
@@ -241,15 +248,11 @@
}
}
-bool TimerThread::NoTimeoutMap::isValidHandle(Handle handle) const {
- return handle > getIndexedHandle(mNoTimeoutRequests);
-}
-
TimerThread::Handle TimerThread::NoTimeoutMap::add(std::shared_ptr<const Request> request) {
std::lock_guard lg(mNTMutex);
// A unique handle is obtained by mNoTimeoutRequests.fetch_add(1),
// This need not be under a lock, but we do so anyhow.
- const Handle handle = getIndexedHandle(mNoTimeoutRequests++);
+ const Handle handle = getUniqueHandle_l();
mMap[handle] = request;
return handle;
}
@@ -271,16 +274,6 @@
}
}
-TimerThread::Handle TimerThread::MonitorThread::getUniqueHandle_l(
- std::chrono::milliseconds timeout) {
- // To avoid key collisions, advance by 1 tick until the key is unique.
- auto deadline = std::chrono::steady_clock::now() + timeout;
- for (; mMonitorRequests.find(deadline) != mMonitorRequests.end();
- deadline += std::chrono::steady_clock::duration(1))
- ;
- return deadline;
-}
-
TimerThread::MonitorThread::MonitorThread(RequestQueue& timeoutQueue)
: mTimeoutQueue(timeoutQueue)
, mThread([this] { threadFunc(); }) {
@@ -300,24 +293,78 @@
void TimerThread::MonitorThread::threadFunc() {
std::unique_lock _l(mMutex);
while (!mShouldExit) {
+ Handle nextDeadline = INVALID_HANDLE;
+ Handle now = INVALID_HANDLE;
if (!mMonitorRequests.empty()) {
- Handle nextDeadline = mMonitorRequests.begin()->first;
- if (nextDeadline < std::chrono::steady_clock::now()) {
+ nextDeadline = mMonitorRequests.begin()->first;
+ now = std::chrono::steady_clock::now();
+ if (nextDeadline < now) {
+ auto node = mMonitorRequests.extract(mMonitorRequests.begin());
// Deadline has expired, handle the request.
+ auto secondChanceDuration = node.mapped().first->secondChanceDuration;
+ if (secondChanceDuration.count() != 0) {
+ // We now apply the second chance duration to find the clock
+ // monotonic second deadline. The unique key is then the
+ // pair<second_deadline, first_deadline>.
+ //
+ // The second chance prevents a false timeout should there be
+ // any clock monotonic advancement during suspend.
+ auto newHandle = now + secondChanceDuration;
+ ALOGD("%s: TimeCheck second chance applied for %s",
+ __func__, node.mapped().first->tag.c_str()); // should be rare event.
+ mSecondChanceRequests.emplace_hint(mSecondChanceRequests.end(),
+ std::make_pair(newHandle, nextDeadline),
+ std::move(node.mapped()));
+ // increment second chance counter.
+ mSecondChanceCount.fetch_add(1 /* arg */, std::memory_order_relaxed);
+ } else {
+ {
+ _l.unlock();
+ // We add Request to retired queue early so that it can be dumped out.
+ mTimeoutQueue.add(std::move(node.mapped().first));
+ node.mapped().second(nextDeadline);
+ // Caution: we don't hold lock when we call TimerCallback,
+ // but this is the timeout case! We will crash soon,
+ // maybe before returning.
+ // anything left over is released here outside lock.
+ }
+ // reacquire the lock - if something was added, we loop immediately to check.
+ _l.lock();
+ }
+ // always process expiring monitor requests first.
+ continue;
+ }
+ }
+ // now process any second chance requests.
+ if (!mSecondChanceRequests.empty()) {
+ Handle secondDeadline = mSecondChanceRequests.begin()->first.first;
+ if (now == INVALID_HANDLE) now = std::chrono::steady_clock::now();
+ if (secondDeadline < now) {
+ auto node = mSecondChanceRequests.extract(mSecondChanceRequests.begin());
{
- auto node = mMonitorRequests.extract(mMonitorRequests.begin());
_l.unlock();
// We add Request to retired queue early so that it can be dumped out.
mTimeoutQueue.add(std::move(node.mapped().first));
- node.mapped().second(); // Caution: we don't hold lock here - but do we care?
- // this is the timeout case! We will crash soon,
- // maybe before returning.
- // anything left over is released here outside lock.
+ const Handle originalHandle = node.key().second;
+ node.mapped().second(originalHandle);
+ // Caution: we don't hold lock when we call TimerCallback.
+ // This is benign issue - we permit concurrent operations
+ // while in the callback to the MonitorQueue.
+ //
+ // Anything left over is released here outside lock.
}
// reacquire the lock - if something was added, we loop immediately to check.
_l.lock();
continue;
}
+ // update the deadline.
+ if (nextDeadline == INVALID_HANDLE) {
+ nextDeadline = secondDeadline;
+ } else {
+ nextDeadline = std::min(nextDeadline, secondDeadline);
+ }
+ }
+ if (nextDeadline != INVALID_HANDLE) {
mCond.wait_until(_l, nextDeadline);
} else {
mCond.wait(_l);
@@ -326,26 +373,39 @@
}
TimerThread::Handle TimerThread::MonitorThread::add(
- std::shared_ptr<const Request> request, std::function<void()>&& func,
- std::chrono::milliseconds timeout) {
+ std::shared_ptr<const Request> request, TimerCallback&& func, Duration timeout) {
std::lock_guard _l(mMutex);
const Handle handle = getUniqueHandle_l(timeout);
- mMonitorRequests.emplace(handle, std::make_pair(std::move(request), std::move(func)));
+ mMonitorRequests.emplace_hint(mMonitorRequests.end(),
+ handle, std::make_pair(std::move(request), std::move(func)));
mCond.notify_all();
return handle;
}
std::shared_ptr<const TimerThread::Request> TimerThread::MonitorThread::remove(Handle handle) {
+ std::pair<std::shared_ptr<const Request>, TimerCallback> data;
std::unique_lock ul(mMutex);
- const auto it = mMonitorRequests.find(handle);
- if (it == mMonitorRequests.end()) {
- return {};
+ if (const auto it = mMonitorRequests.find(handle);
+ it != mMonitorRequests.end()) {
+ data = std::move(it->second);
+ mMonitorRequests.erase(it);
+ ul.unlock(); // manually release lock here so func (data.second)
+ // is released outside of lock.
+ return data.first; // request
}
- std::shared_ptr<const TimerThread::Request> request = std::move(it->second.first);
- std::function<void()> func = std::move(it->second.second);
- mMonitorRequests.erase(it);
- ul.unlock(); // manually release lock here so func is released outside of lock.
- return request;
+
+ // this check is O(N), but since the second chance requests are ordered
+ // in terms of earliest expiration time, we would expect better than average results.
+ for (auto it = mSecondChanceRequests.begin(); it != mSecondChanceRequests.end(); ++it) {
+ if (it->first.second == handle) {
+ data = std::move(it->second);
+ mSecondChanceRequests.erase(it);
+ ul.unlock(); // manually release lock here so func (data.second)
+ // is released outside of lock.
+ return data.first; // request
+ }
+ }
+ return {};
}
void TimerThread::MonitorThread::copyRequests(
@@ -354,6 +414,13 @@
for (const auto &[deadline, monitorpair] : mMonitorRequests) {
requests.emplace_back(monitorpair.first);
}
+ // we combine the second map with the first map - this is
+ // everything that is pending on the monitor thread.
+ // The second map will be older than the first map so this
+ // is in order.
+ for (const auto &[deadline, monitorpair] : mSecondChanceRequests) {
+ requests.emplace_back(monitorpair.first);
+ }
}
} // namespace android::mediautils
diff --git a/media/utils/fuzzers/TimeCheckFuzz.cpp b/media/utils/fuzzers/TimeCheckFuzz.cpp
index 7966469..65b2885 100644
--- a/media/utils/fuzzers/TimeCheckFuzz.cpp
+++ b/media/utils/fuzzers/TimeCheckFuzz.cpp
@@ -48,7 +48,9 @@
std::string name = data_provider.ConsumeRandomLengthString(kMaxStringLen);
// 3. The constructor, which is fuzzed here:
- android::mediautils::TimeCheck timeCheck(name.c_str(), {} /* onTimer */, timeoutMs);
+ android::mediautils::TimeCheck timeCheck(name.c_str(), {} /* onTimer */,
+ std::chrono::milliseconds(timeoutMs),
+ {} /* secondChanceDuration */, true /* crashOnTimeout */);
// We will leave some buffer to avoid sleeping too long
uint8_t sleep_amount_ms = data_provider.ConsumeIntegralInRange<uint8_t>(0, timeoutMs / 2);
diff --git a/media/utils/include/mediautils/FixedString.h b/media/utils/include/mediautils/FixedString.h
new file mode 100644
index 0000000..047aa82
--- /dev/null
+++ b/media/utils/include/mediautils/FixedString.h
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <string>
+#include <string_view>
+
+namespace android::mediautils {
+
+/*
+ * FixedString is a stack allocatable string buffer that supports
+ * simple appending of other strings and string_views.
+ *
+ * It is designed for no-malloc operation when std::string
+ * small buffer optimization is insufficient.
+ *
+ * To keep code small, use asStringView() for operations on this.
+ *
+ * Notes:
+ * 1) Appending beyond the internal buffer size results in truncation.
+ *
+ * Alternatives:
+ * 1) If you want a sharable copy-on-write string implementation,
+ * consider using the legacy android::String8().
+ * 2) Using std::string with a fixed stack allocator may suit your needs,
+ * but exception avoidance is tricky.
+ * 3) Using C++20 ranges https://en.cppreference.com/w/cpp/ranges if you don't
+ * need backing store. Be careful about allocation with ranges.
+ *
+ * Good small sizes are multiples of 16 minus 2, e.g. 14, 30, 46, 62.
+ *
+ * Implementation Notes:
+ * 1) No iterators or [] for FixedString - please convert to std::string_view.
+ * 2) For small N (e.g. less than 62), consider a change to always zero fill and
+ * potentially prevent always zero terminating (if one only does append).
+ *
+ * Possible arguments to create/append:
+ * 1) A FixedString.
+ * 2) A single char.
+ * 3) A char * pointer.
+ * 4) A std::string.
+ * 5) A std::string_view (or something convertible to it).
+ *
+ * Example:
+ *
+ * FixedString s1(std::string("a")); // ctor
+ * s1 << "bc" << 'd' << '\n'; // streaming append
+ * s1 += "hello"; // += append
+ * ASSERT_EQ(s1, "abcd\nhello");
+ */
+template <uint32_t N>
+struct FixedString
+{
+ // Find the best size type.
+ using strsize_t = std::conditional_t<(N > 255), uint32_t, uint8_t>;
+
+ // constructors
+ FixedString() { // override default
+ buffer_[0] = '\0';
+ }
+
+ FixedString(const FixedString& other) { // override default.
+ copyFrom<N>(other);
+ }
+
+ // The following constructor is not explicit to allow
+ // FixedString<8> s = "abcd";
+ template <typename ...Types>
+ FixedString(Types&&... args) {
+ append(std::forward<Types>(args)...);
+ }
+
+ // copy assign (copyFrom checks for equality and returns *this).
+ FixedString& operator=(const FixedString& other) { // override default.
+ return copyFrom<N>(other);
+ }
+
+ template <typename ...Types>
+ FixedString& operator=(Types&&... args) {
+ size_ = 0;
+ return append(std::forward<Types>(args)...);
+ }
+
+ // operator equals
+ bool operator==(const char *s) const {
+ return strncmp(c_str(), s, capacity() + 1) == 0;
+ }
+
+ bool operator==(std::string_view s) const {
+ return size() == s.size() && memcmp(data(), s.data(), size()) == 0;
+ }
+
+ // operator not-equals
+ template <typename T>
+ bool operator!=(const T& other) const {
+ return !operator==(other);
+ }
+
+ // operator +=
+ template <typename ...Types>
+ FixedString& operator+=(Types&&... args) {
+ return append(std::forward<Types>(args)...);
+ }
+
+ // conversion to std::string_view.
+ operator std::string_view() const {
+ return asStringView();
+ }
+
+ // basic observers
+ size_t buffer_offset() const { return offsetof(std::decay_t<decltype(*this)>, buffer_); }
+ static constexpr uint32_t capacity() { return N; }
+ uint32_t size() const { return size_; }
+ uint32_t remaining() const { return size_ >= N ? 0 : N - size_; }
+ bool empty() const { return size_ == 0; }
+ bool full() const { return size_ == N; } // when full, possible truncation risk.
+ char * data() { return buffer_; }
+ const char * data() const { return buffer_; }
+ const char * c_str() const { return buffer_; }
+
+ inline std::string_view asStringView() const {
+ return { buffer_, static_cast<size_t>(size_) };
+ }
+ inline std::string asString() const {
+ return { buffer_, static_cast<size_t>(size_) };
+ }
+
+ void clear() { size_ = 0; buffer_[0] = 0; }
+
+ // Implementation of append - using templates
+ // to guarantee precedence in the presence of ambiguity.
+ //
+ // Consider C++20 template overloading through constraints and concepts.
+ template <typename T>
+ FixedString& append(const T& t) {
+ using decayT = std::decay_t<T>;
+ if constexpr (is_specialization_v<decayT, FixedString>) {
+ // A FixedString<U>
+ if (size_ == 0) {
+ // optimization to erase everything.
+ return copyFrom(t);
+ } else {
+ return appendStringView({t.data(), t.size()});
+ }
+ } else if constexpr(std::is_same_v<decayT, char>) {
+ if (size_ < N) {
+ buffer_[size_++] = t;
+ buffer_[size_] = '\0';
+ }
+ return *this;
+ } else if constexpr(std::is_same_v<decayT, char *>) {
+ // Some char* ptr.
+ return appendString(t);
+ } else if constexpr (std::is_convertible_v<decayT, std::string_view>) {
+ // std::string_view, std::string, or some other convertible type.
+ return appendStringView(t);
+ } else /* constexpr */ {
+ static_assert(dependent_false_v<T>, "non-matching append type");
+ }
+ }
+
+ FixedString& appendStringView(std::string_view s) {
+ uint32_t total = std::min(static_cast<size_t>(N - size_), s.size());
+ memcpy(buffer_ + size_, s.data(), total);
+ size_ += total;
+ buffer_[size_] = '\0';
+ return *this;
+ }
+
+ FixedString& appendString(const char *s) {
+ // strncpy zero pads to the end,
+ // strlcpy returns total expected length,
+ // we don't have strncpy_s in Bionic,
+ // so we write our own here.
+ while (size_ < N && *s != '\0') {
+ buffer_[size_++] = *s++;
+ }
+ buffer_[size_] = '\0';
+ return *this;
+ }
+
+ // Copy initialize the struct.
+ // Note: We are POD but customize the copying for acceleration
+ // of moving small strings embedded in a large buffers.
+ template <uint32_t U>
+ FixedString& copyFrom(const FixedString<U>& other) {
+ if ((void*)this != (void*)&other) { // not a self-assignment
+ if (other.size() == 0) {
+ size_ = 0;
+ buffer_[0] = '\0';
+ return *this;
+ }
+ constexpr size_t kSizeToCopyWhole = 64;
+ if constexpr (N == U &&
+ sizeof(*this) == sizeof(other) &&
+ sizeof(*this) <= kSizeToCopyWhole) {
+ // As we have the same str size type, we can just
+ // memcpy with fixed size, which can be easily optimized.
+ memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this));
+ return *this;
+ }
+ if constexpr (std::is_same_v<strsize_t, typename FixedString<U>::strsize_t>) {
+ constexpr size_t kAlign = 8; // align to a multiple of 8.
+ static_assert((kAlign & (kAlign - 1)) == 0); // power of 2.
+ // we check any alignment issues.
+ if (buffer_offset() == other.buffer_offset() && other.size() <= capacity()) {
+ // improve on standard POD copying by reducing size.
+ const size_t mincpy = buffer_offset() + other.size() + 1 /* nul */;
+ const size_t maxcpy = std::min(sizeof(*this), sizeof(other));
+ const size_t cpysize = std::min(mincpy + kAlign - 1 & ~(kAlign - 1), maxcpy);
+ memcpy(static_cast<void*>(this), static_cast<const void*>(&other), cpysize);
+ return *this;
+ }
+ }
+ size_ = std::min(other.size(), capacity());
+ memcpy(buffer_, other.data(), size_);
+ buffer_[size_] = '\0'; // zero terminate.
+ }
+ return *this;
+ }
+
+private:
+ // Template helper methods
+
+ template <typename Test, template <uint32_t> class Ref>
+ struct is_specialization : std::false_type {};
+
+ template <template <uint32_t> class Ref, uint32_t UU>
+ struct is_specialization<Ref<UU>, Ref>: std::true_type {};
+
+ template <typename Test, template <uint32_t> class Ref>
+ static inline constexpr bool is_specialization_v = is_specialization<Test, Ref>::value;
+
+ // For static assert(false) we need a template version to avoid early failure.
+ template <typename T>
+ static inline constexpr bool dependent_false_v = false;
+
+ // POD variables
+ strsize_t size_ = 0;
+ char buffer_[N + 1 /* allow zero termination */];
+};
+
+// Stream operator syntactic sugar.
+// Example:
+// s << 'b' << "c" << "d" << '\n';
+template <uint32_t N, typename ...Types>
+FixedString<N>& operator<<(FixedString<N>& fs, Types&&... args) {
+ return fs.append(std::forward<Types>(args)...);
+}
+
+// We do not use a default size for fixed string as changing
+// the default size would lead to different behavior - we want the
+// size to be explicitly known.
+
+// FixedString62 of 62 chars fits in one typical cache line.
+using FixedString62 = FixedString<62>;
+
+// Slightly smaller
+using FixedString30 = FixedString<30>;
+
+// Since we have added copy and assignment optimizations,
+// we are no longer trivially assignable and copyable.
+// But we check standard layout here to prevent inclusion of unacceptable members or virtuals.
+static_assert(std::is_standard_layout_v<FixedString62>);
+static_assert(std::is_standard_layout_v<FixedString30>);
+
+} // namespace android::mediautils
diff --git a/media/utils/include/mediautils/MethodStatistics.h b/media/utils/include/mediautils/MethodStatistics.h
index 700fbaa..6d7e990 100644
--- a/media/utils/include/mediautils/MethodStatistics.h
+++ b/media/utils/include/mediautils/MethodStatistics.h
@@ -55,15 +55,23 @@
/**
* Adds a method event, typically execution time in ms.
*/
- void event(Code code, FloatType executeMs) {
+ template <typename C>
+ void event(C&& code, FloatType executeMs) {
std::lock_guard lg(mLock);
- mStatisticsMap[code].add(executeMs);
+ auto it = mStatisticsMap.lower_bound(code);
+ if (it != mStatisticsMap.end() && it->first == code) {
+ it->second.add(executeMs);
+ } else {
+ // StatsType ctor takes an optional array of data for initialization.
+ FloatType dataArray[1] = { executeMs };
+ mStatisticsMap.emplace_hint(it, std::forward<C>(code), dataArray);
+ }
}
/**
* Returns the name for the method code.
*/
- std::string getMethodForCode(Code code) const {
+ std::string getMethodForCode(const Code& code) const {
auto it = mMethodMap.find(code);
return it == mMethodMap.end() ? std::to_string((int)code) : it->second;
}
@@ -71,7 +79,7 @@
/**
* Returns the number of times the method was invoked by event().
*/
- size_t getMethodCount(Code code) const {
+ size_t getMethodCount(const Code& code) const {
std::lock_guard lg(mLock);
auto it = mStatisticsMap.find(code);
return it == mStatisticsMap.end() ? 0 : it->second.getN();
@@ -80,7 +88,7 @@
/**
* Returns the statistics object for the method.
*/
- StatsType getStatistics(Code code) const {
+ StatsType getStatistics(const Code& code) const {
std::lock_guard lg(mLock);
auto it = mStatisticsMap.find(code);
return it == mStatisticsMap.end() ? StatsType{} : it->second;
@@ -107,9 +115,10 @@
}
private:
- const std::map<Code, std::string> mMethodMap;
+ // Note: we use a transparent comparator std::less<> for heterogeneous key lookup.
+ const std::map<Code, std::string, std::less<>> mMethodMap;
mutable std::mutex mLock;
- std::map<Code, StatsType> mStatisticsMap GUARDED_BY(mLock);
+ std::map<Code, StatsType, std::less<>> mStatisticsMap GUARDED_BY(mLock);
};
// Managed Statistics support.
diff --git a/media/utils/include/mediautils/TimeCheck.h b/media/utils/include/mediautils/TimeCheck.h
index ef03aef..bdb5337 100644
--- a/media/utils/include/mediautils/TimeCheck.h
+++ b/media/utils/include/mediautils/TimeCheck.h
@@ -16,6 +16,7 @@
#pragma once
+#include <chrono>
#include <vector>
#include <mediautils/TimerThread.h>
@@ -27,10 +28,33 @@
class TimeCheck {
public:
+
+ // Duration for TimeCheck is based on steady_clock, typically nanoseconds.
+ using Duration = std::chrono::steady_clock::duration;
+
+ // Duration for printing is in milliseconds, using float for additional precision.
+ using FloatMs = std::chrono::duration<float, std::milli>;
+
+ // OnTimerFunc is the callback function with 2 parameters.
+ // bool timeout (which is true when the TimeCheck object
+ // times out, false when the TimeCheck object is
+ // destroyed or leaves scope before the timer expires.)
+ // float elapsedMs (the elapsed time to this event).
using OnTimerFunc = std::function<void(bool /* timeout */, float /* elapsedMs */ )>;
// The default timeout is chosen to be less than system server watchdog timeout
- static constexpr uint32_t kDefaultTimeOutMs = 5000;
+ // Note: kDefaultTimeOutMs should be no less than 2 seconds, otherwise spurious timeouts
+ // may occur with system suspend.
+ static constexpr TimeCheck::Duration kDefaultTimeoutDuration = std::chrono::milliseconds(3000);
+
+ // Due to suspend abort not incrementing the monotonic clock,
+ // we allow another second chance timeout after the first timeout expires.
+ //
+ // The total timeout is therefore kDefaultTimeoutDuration + kDefaultSecondChanceDuration,
+ // and the result is more stable when the monotonic clock increments during suspend.
+ //
+ static constexpr TimeCheck::Duration kDefaultSecondChanceDuration =
+ std::chrono::milliseconds(2000);
/**
* TimeCheck is a RAII object which will notify a callback
@@ -44,24 +68,24 @@
* the deallocation.
*
* \param tag string associated with the TimeCheck object.
- * \param onTimer callback function with 2 parameters
- * bool timeout (which is true when the TimeCheck object
- * times out, false when the TimeCheck object is
- * destroyed or leaves scope before the timer expires.)
- * float elapsedMs (the elapsed time to this event).
+ * \param onTimer callback function with 2 parameters (described above in OnTimerFunc).
* The callback when timeout is true will be called on a different thread.
* This will cancel the callback on the destructor but is not guaranteed
* to block for callback completion if it is already in progress
* (for maximum concurrency and reduced deadlock potential), so use proper
* lifetime analysis (e.g. shared or weak pointers).
- * \param timeoutMs timeout in milliseconds.
+ * \param requestedTimeoutDuration timeout in milliseconds.
* A zero timeout means no timeout is set -
* the callback is called only when
* the TimeCheck object is destroyed or leaves scope.
+ * \param secondChanceDuration additional milliseconds to wait if the first timeout expires.
+ * This is used to prevent false timeouts if the steady (monotonic)
+ * clock advances on aborted suspend.
* \param crashOnTimeout true if the object issues an abort on timeout.
*/
- explicit TimeCheck(std::string tag, OnTimerFunc&& onTimer = {},
- uint32_t timeoutMs = kDefaultTimeOutMs, bool crashOnTimeout = true);
+ explicit TimeCheck(std::string_view tag, OnTimerFunc&& onTimer,
+ Duration requestedTimeoutDuration, Duration secondChanceDuration,
+ bool crashOnTimeout);
TimeCheck() = default;
// Remove copy constructors as there should only be one call to the destructor.
@@ -79,16 +103,36 @@
// The usage here is const safe.
class TimeCheckHandler {
public:
- const std::string tag;
+ template <typename S, typename F>
+ TimeCheckHandler(S&& _tag, F&& _onTimer, bool _crashOnTimeout,
+ Duration _timeoutDuration, Duration _secondChanceDuration,
+ std::chrono::system_clock::time_point _startSystemTime,
+ pid_t _tid)
+ : tag(std::forward<S>(_tag))
+ , onTimer(std::forward<F>(_onTimer))
+ , crashOnTimeout(_crashOnTimeout)
+ , timeoutDuration(_timeoutDuration)
+ , secondChanceDuration(_secondChanceDuration)
+ , startSystemTime(_startSystemTime)
+ , tid(_tid)
+ {}
+ const FixedString62 tag;
const OnTimerFunc onTimer;
const bool crashOnTimeout;
- const std::chrono::system_clock::time_point startTime;
+ const Duration timeoutDuration;
+ const Duration secondChanceDuration;
+ const std::chrono::system_clock::time_point startSystemTime;
const pid_t tid;
void onCancel(TimerThread::Handle handle) const;
- void onTimeout() const;
+ void onTimeout(TimerThread::Handle handle) const;
};
+ // Returns a string that represents the timeout vs elapsed time,
+ // and diagnostics if there are any potential issues.
+ static std::string analyzeTimeouts(
+ float timeoutMs, float elapsedSteadyMs, float elapsedSystemMs);
+
static TimerThread& getTimeCheckThread();
static void accessAudioHalPids(std::vector<pid_t>* pids, bool update);
diff --git a/media/utils/include/mediautils/TimerThread.h b/media/utils/include/mediautils/TimerThread.h
index ffee602..c76fa7d 100644
--- a/media/utils/include/mediautils/TimerThread.h
+++ b/media/utils/include/mediautils/TimerThread.h
@@ -27,6 +27,8 @@
#include <android-base/thread_annotations.h>
+#include <mediautils/FixedString.h>
+
namespace android::mediautils {
/**
@@ -34,19 +36,93 @@
*/
class TimerThread {
public:
- // A Handle is a time_point that serves as a unique key. It is ordered.
+ // A Handle is a time_point that serves as a unique key to access a queued
+ // request to the TimerThread.
using Handle = std::chrono::steady_clock::time_point;
+ // Duration is based on steady_clock (typically nanoseconds)
+ // vs the system_clock duration (typically microseconds).
+ using Duration = std::chrono::steady_clock::duration;
+
static inline constexpr Handle INVALID_HANDLE =
std::chrono::steady_clock::time_point::min();
+ // Handle implementation details:
+ // A Handle represents the timer expiration time based on std::chrono::steady_clock
+ // (clock monotonic). This Handle is computed as now() + timeout.
+ //
+ // The lsb of the Handle time_point is adjusted to indicate whether there is
+ // a timeout action (1) or not (0).
+ //
+
+ template <size_t COUNT>
+ static constexpr bool is_power_of_2_v = COUNT > 0 && (COUNT & (COUNT - 1)) == 0;
+
+ template <size_t COUNT>
+ static constexpr size_t mask_from_count_v = COUNT - 1;
+
+ static constexpr size_t HANDLE_TYPES = 2;
+ // HANDLE_TYPES must be a power of 2.
+ static_assert(is_power_of_2_v<HANDLE_TYPES>);
+
+ // The handle types
+ enum class HANDLE_TYPE : size_t {
+ NO_TIMEOUT = 0,
+ TIMEOUT = 1,
+ };
+
+ static constexpr size_t HANDLE_TYPE_MASK = mask_from_count_v<HANDLE_TYPES>;
+
+ template <typename T>
+ static constexpr auto enum_as_value(T x) {
+ return static_cast<std::underlying_type_t<T>>(x);
+ }
+
+ static inline bool isNoTimeoutHandle(Handle handle) {
+ return (handle.time_since_epoch().count() & HANDLE_TYPE_MASK) ==
+ enum_as_value(HANDLE_TYPE::NO_TIMEOUT);
+ }
+
+ static inline bool isTimeoutHandle(Handle handle) {
+ return (handle.time_since_epoch().count() & HANDLE_TYPE_MASK) ==
+ enum_as_value(HANDLE_TYPE::TIMEOUT);
+ }
+
+ // Returns a unique Handle that doesn't exist in the container.
+ template <size_t MAX_TYPED_HANDLES, size_t HANDLE_TYPE_AS_VALUE, typename C, typename T>
+ static Handle getUniqueHandleForHandleType_l(C container, T timeout) {
+ static_assert(MAX_TYPED_HANDLES > 0 && HANDLE_TYPE_AS_VALUE < MAX_TYPED_HANDLES
+ && is_power_of_2_v<MAX_TYPED_HANDLES>,
+ " handles must be power of two");
+
+ // Our initial handle is the deadline as computed from steady_clock.
+ auto deadline = std::chrono::steady_clock::now() + timeout;
+
+ // We adjust the lsbs by the minimum increment to have the correct
+ // HANDLE_TYPE in the least significant bits.
+ auto remainder = deadline.time_since_epoch().count() & HANDLE_TYPE_MASK;
+ size_t offset = HANDLE_TYPE_AS_VALUE > remainder ? HANDLE_TYPE_AS_VALUE - remainder :
+ MAX_TYPED_HANDLES + HANDLE_TYPE_AS_VALUE - remainder;
+ deadline += std::chrono::steady_clock::duration(offset);
+
+ // To avoid key collisions, advance the handle by MAX_TYPED_HANDLES (the modulus factor)
+ // until the key is unique.
+ while (container.find(deadline) != container.end()) {
+ deadline += std::chrono::steady_clock::duration(MAX_TYPED_HANDLES);
+ }
+ return deadline;
+ }
+
+ // TimerCallback invoked on timeout or cancel.
+ using TimerCallback = std::function<void(Handle)>;
+
/**
* Schedules a task to be executed in the future (`timeout` duration from now).
*
* \param tag string associated with the task. This need not be unique,
* as the Handle returned is used for cancelling.
* \param func callback function that is invoked at the timeout.
- * \param timeout timeout duration which is converted to milliseconds with at
+ * \param timeoutDuration timeout duration which is converted to milliseconds with at
* least 45 integer bits.
* A timeout of 0 (or negative) means the timer never expires
* so func() is never called. These tasks are stored internally
@@ -54,7 +130,8 @@
* \returns a handle that can be used for cancellation.
*/
Handle scheduleTask(
- std::string tag, std::function<void()>&& func, std::chrono::milliseconds timeout);
+ std::string_view tag, TimerCallback&& func,
+ Duration timeoutDuration, Duration secondChanceDuration);
/**
* Tracks a task that shows up on toString() until cancelled.
@@ -62,7 +139,7 @@
* \param tag string associated with the task.
* \returns a handle that can be used for cancellation.
*/
- Handle trackTask(std::string tag);
+ Handle trackTask(std::string_view tag);
/**
* Cancels a task previously scheduled with scheduleTask()
@@ -128,14 +205,30 @@
private:
// To minimize movement of data, we pass around shared_ptrs to Requests.
// These are allocated and deallocated outside of the lock.
+ // TODO(b/243839867) consider options to merge Request with the
+ // TimeCheck::TimeCheckHandler struct.
struct Request {
+ Request(std::chrono::system_clock::time_point _scheduled,
+ std::chrono::system_clock::time_point _deadline,
+ Duration _secondChanceDuration,
+ pid_t _tid,
+ std::string_view _tag)
+ : scheduled(_scheduled)
+ , deadline(_deadline)
+ , secondChanceDuration(_secondChanceDuration)
+ , tid(_tid)
+ , tag(_tag)
+ {}
+
const std::chrono::system_clock::time_point scheduled;
- const std::chrono::system_clock::time_point deadline; // deadline := scheduled + timeout
+ const std::chrono::system_clock::time_point deadline; // deadline := scheduled
+ // + timeoutDuration
+ // + secondChanceDuration
// if deadline == scheduled, no
// timeout, task not executed.
+ Duration secondChanceDuration;
const pid_t tid;
- const std::string tag;
-
+ const FixedString62 tag;
std::string toString() const;
};
@@ -160,15 +253,17 @@
mRequestQueue GUARDED_BY(mRQMutex);
};
- // A storage map of tasks without timeouts. There is no std::function<void()>
+ // A storage map of tasks without timeouts. There is no TimerCallback
// required, it just tracks the tasks with the tag, scheduled time and the tid.
// These tasks show up on a pendingToString() until manually cancelled.
class NoTimeoutMap {
- // This a counter of the requests that have no timeout (timeout == 0).
- std::atomic<size_t> mNoTimeoutRequests{};
-
mutable std::mutex mNTMutex;
std::map<Handle, std::shared_ptr<const Request>> mMap GUARDED_BY(mNTMutex);
+ Handle getUniqueHandle_l() REQUIRES(mNTMutex) {
+ return getUniqueHandleForHandleType_l<
+ HANDLE_TYPES, enum_as_value(HANDLE_TYPE::NO_TIMEOUT)>(
+ mMap, Duration{} /* timeout */);
+ }
public:
bool isValidHandle(Handle handle) const; // lock free
@@ -182,14 +277,26 @@
// call on timeout.
// This class is thread-safe.
class MonitorThread {
+ std::atomic<size_t> mSecondChanceCount{};
mutable std::mutex mMutex;
- mutable std::condition_variable mCond;
+ mutable std::condition_variable mCond GUARDED_BY(mMutex);
// Ordered map of requests based on time of deadline.
//
- std::map<Handle, std::pair<std::shared_ptr<const Request>, std::function<void()>>>
+ std::map<Handle, std::pair<std::shared_ptr<const Request>, TimerCallback>>
mMonitorRequests GUARDED_BY(mMutex);
+ // Due to monotonic/steady clock inaccuracies during suspend,
+ // we allow an additional second chance waiting time to prevent
+ // false removal.
+
+ // This mSecondChanceRequests queue is almost always empty.
+ // Using a pair with the original handle allows lookup and keeps
+ // the Key unique.
+ std::map<std::pair<Handle /* new */, Handle /* original */>,
+ std::pair<std::shared_ptr<const Request>, TimerCallback>>
+ mSecondChanceRequests GUARDED_BY(mMutex);
+
RequestQueue& mTimeoutQueue; // locked internally, added to when request times out.
// Worker thread variables
@@ -200,16 +307,23 @@
std::thread mThread;
void threadFunc();
- Handle getUniqueHandle_l(std::chrono::milliseconds timeout) REQUIRES(mMutex);
+ Handle getUniqueHandle_l(Duration timeout) REQUIRES(mMutex) {
+ return getUniqueHandleForHandleType_l<
+ HANDLE_TYPES, enum_as_value(HANDLE_TYPE::TIMEOUT)>(
+ mMonitorRequests, timeout);
+ }
public:
MonitorThread(RequestQueue &timeoutQueue);
~MonitorThread();
- Handle add(std::shared_ptr<const Request> request, std::function<void()>&& func,
- std::chrono::milliseconds timeout);
+ Handle add(std::shared_ptr<const Request> request, TimerCallback&& func,
+ Duration timeout);
std::shared_ptr<const Request> remove(Handle handle);
void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
+ size_t getSecondChanceCount() const {
+ return mSecondChanceCount.load(std::memory_order_relaxed);
+ }
};
// Analysis contains info deduced by analysisTimeout().
@@ -244,16 +358,6 @@
std::vector<std::shared_ptr<const Request>> getPendingRequests() const;
- // A no-timeout request is represented by a handles at the end of steady_clock time,
- // counting down by the number of no timeout requests previously requested.
- // We manage them on the NoTimeoutMap, but conceptually they could be scheduled
- // on the MonitorThread because those time handles won't expire in
- // the lifetime of the device.
- static inline Handle getIndexedHandle(size_t index) {
- return std::chrono::time_point<std::chrono::steady_clock>::max() -
- std::chrono::time_point<std::chrono::steady_clock>::duration(index);
- }
-
static constexpr size_t kRetiredQueueMax = 16;
RequestQueue mRetiredQueue{kRetiredQueueMax}; // locked internally
diff --git a/media/utils/tests/Android.bp b/media/utils/tests/Android.bp
index 1024018..232cc4e 100644
--- a/media/utils/tests/Android.bp
+++ b/media/utils/tests/Android.bp
@@ -124,6 +124,27 @@
}
cc_test {
+ name: "mediautils_fixedstring_tests",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+
+ shared_libs: [
+ "libaudioutils",
+ "liblog",
+ "libmediautils",
+ "libutils",
+ ],
+
+ srcs: [
+ "mediautils_fixedstring_tests.cpp",
+ ],
+}
+
+cc_test {
name: "mediautils_scopedstatistics_tests",
cflags: [
diff --git a/media/utils/tests/mediautils_fixedstring_tests.cpp b/media/utils/tests/mediautils_fixedstring_tests.cpp
new file mode 100644
index 0000000..7ab9a9f
--- /dev/null
+++ b/media/utils/tests/mediautils_fixedstring_tests.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "mediautils_fixedstring_tests"
+
+#include <mediautils/FixedString.h>
+
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+using namespace android::mediautils;
+
+TEST(mediautils_fixedstring_tests, ctor) {
+ FixedString<8> s0("abcde");
+
+ ASSERT_FALSE(s0.empty());
+ ASSERT_EQ(8U, s0.capacity());
+
+ ASSERT_EQ(5U, s0.size());
+ ASSERT_EQ(3U, s0.remaining());
+ ASSERT_EQ(0, strcmp(s0.c_str(), "abcde"));
+
+ ASSERT_EQ(0, strcmp(s0.data(), "abcde"));
+
+ // overflow
+ FixedString<8> s1("abcdefghijk");
+ ASSERT_EQ(8U, s1.size());
+ ASSERT_TRUE(s1.full());
+ ASSERT_EQ(0U, s1.remaining());
+ ASSERT_EQ(0, strcmp(s1.c_str(), "abcdefgh"));
+
+ // overflow
+ FixedString<8> s2(std::string("abcdefghijk"));
+ ASSERT_TRUE(s2.full());
+
+ ASSERT_EQ(8U, s2.size());
+ ASSERT_EQ(0, strcmp(s2.c_str(), "abcdefgh"));
+
+ // complex
+ ASSERT_EQ(s1, s2);
+ ASSERT_EQ(FixedString<12>().append(s1), s2);
+ ASSERT_NE(s1, "bcd");
+
+ // string and stringview
+ ASSERT_EQ(s1.asString(), s1.asStringView());
+
+ FixedString30 s3;
+ s3 = std::string("abcd");
+ ASSERT_EQ(s3, "abcd");
+
+ s3.clear();
+ ASSERT_EQ(s3, "");
+ ASSERT_NE(s3, "abcd");
+ ASSERT_EQ(0U, s3.size());
+}
+
+TEST(mediautils_fixedstring_tests, append) {
+ FixedString<8> s0;
+ ASSERT_EQ(0U, s0.size());
+ ASSERT_EQ(0, strcmp(s0.c_str(), ""));
+ ASSERT_TRUE(s0.empty());
+ ASSERT_FALSE(s0.full());
+
+ s0.append("abc");
+ ASSERT_EQ(3U, s0.size());
+ ASSERT_EQ(0, strcmp(s0.c_str(), "abc"));
+
+ s0.append(std::string("d"));
+ ASSERT_EQ(4U, s0.size());
+ ASSERT_EQ(0, strcmp(s0.c_str(), "abcd"));
+
+ // overflow
+ s0.append("efghijk");
+ ASSERT_EQ(8U, s0.size());
+ ASSERT_EQ(0, strcmp(s0.c_str(), "abcdefgh"));
+ ASSERT_TRUE(s0.full());
+
+ // concatenated
+ ASSERT_EQ(FixedString62("abcd"),
+ FixedString<8>("ab").append("c").append(FixedString<8>("d")));
+ ASSERT_EQ(FixedString<12>("a").append(FixedString<12>("b")), "ab");
+}
+
+TEST(mediautils_fixedstring_tests, plus_equals) {
+ FixedString<8> s0;
+ ASSERT_EQ(0U, s0.size());
+ ASSERT_EQ(0, strcmp(s0.c_str(), ""));
+
+ s0 += "abc";
+ s0 += "def";
+ ASSERT_EQ(s0, "abcdef");
+}
+
+TEST(mediautils_fixedstring_tests, stream_operator) {
+ FixedString<8> s0('a');
+
+ s0 << 'b' << "c" << "d" << '\n';
+ ASSERT_EQ(s0, "abcd\n");
+}
+
+TEST(mediautils_fixedstring_tests, examples) {
+ FixedString30 s1(std::string("a"));
+ s1 << "bc" << 'd' << '\n';
+ s1 += "hello";
+
+ ASSERT_EQ(s1, "abcd\nhello");
+
+ FixedString30 s2;
+ for (const auto &c : s1.asStringView()) {
+ s2.append(c);
+ };
+ ASSERT_EQ(s1, s2);
+
+ FixedString30 s3(std::move(s1));
+}
+
+// Ensure type alias works fine as well.
+using FixedString1024 = FixedString<1024>;
+
+TEST(mediautils_fixedstring_tests, copy) {
+ FixedString1024 s0("abc");
+ FixedString62 s1(s0);
+
+ ASSERT_EQ(3U, s1.size());
+ ASSERT_EQ(0, strcmp(s1.c_str(), "abc"));
+ ASSERT_EQ(s0, s1);
+
+ FixedString<1024> s2(s1);
+ ASSERT_EQ(3U, s2.size());
+ ASSERT_EQ(0, strcmp(s2.c_str(), "abc"));
+ ASSERT_EQ(s2, "abc");
+ ASSERT_NE(s2, "def");
+ ASSERT_EQ(s2, std::string("abc"));
+ ASSERT_NE(s2, std::string("def"));
+ ASSERT_EQ(s1, s2);
+ ASSERT_EQ(s0, s2);
+ ASSERT_EQ(s2, FixedString62(FixedString1024("abc")));
+}
diff --git a/media/utils/tests/timecheck_tests.cpp b/media/utils/tests/timecheck_tests.cpp
index 6ebf44d..8236174 100644
--- a/media/utils/tests/timecheck_tests.cpp
+++ b/media/utils/tests/timecheck_tests.cpp
@@ -39,7 +39,7 @@
timeoutRegistered = timeout;
elapsedMsRegistered = elapsedMs;
event = true;
- }, 1000 /* msec */, false /* crash */);
+ }, 1000ms /* timeoutDuration */, {} /* secondChanceDuration */, false /* crash */);
}
ASSERT_TRUE(event);
ASSERT_FALSE(timeoutRegistered);
@@ -58,7 +58,7 @@
timeoutRegistered = timeout;
elapsedMsRegistered = elapsedMs;
event = true; // store-release, must be last.
- }, 1 /* msec */, false /* crash */);
+ }, 1ms /* timeoutDuration */, {} /* secondChanceDuration */, false /* crash */);
std::this_thread::sleep_for(100ms);
}
ASSERT_TRUE(event); // load-acquire, must be first.
@@ -69,4 +69,4 @@
// Note: We do not test TimeCheck crash because TimeCheck is multithreaded and the
// EXPECT_EXIT() signal catching is imperfect due to the gtest fork.
-} // namespace
\ No newline at end of file
+} // namespace
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 8ae06d0..4653f96 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -4602,7 +4602,9 @@
} else {
getIAudioFlingerStatistics().event(code, elapsedMs);
}
- });
+ }, mediautils::TimeCheck::kDefaultTimeoutDuration,
+ mediautils::TimeCheck::kDefaultSecondChanceDuration,
+ true /* crashOnTimeout */);
// Make sure we connect to Audio Policy Service before calling into AudioFlinger:
// - AudioFlinger can call into Audio Policy Service with its global mutex held
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index 72c378d..a80d729 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -1809,7 +1809,7 @@
} else {
getIEffectStatistics().event(code, elapsedMs);
}
- }, 0 /* timeoutMs */);
+ }, {} /* timeoutDuration */, {} /* secondChanceDuration */, false /* crashOnTimeout */);
return BnEffect::onTransact(code, data, reply, flags);
}
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 533e0b3..3d44aec 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -178,6 +178,11 @@
// Direct output thread minimum sleep time in idle or active(underrun) state
static const nsecs_t kDirectMinSleepTimeUs = 10000;
+// Minimum amount of time between checking to see if the timestamp is advancing
+// for underrun detection. If we check too frequently, we may not detect a
+// timestamp update and will falsely detect underrun.
+static const nsecs_t kMinimumTimeBetweenTimestampChecksNs = 150 /* ms */ * 1000;
+
// The universal constant for ubiquitous 20ms value. The value of 20ms seems to provide a good
// balance between power consumption and latency, and allows threads to be scheduled reliably
// by the CFS scheduler.
@@ -2040,7 +2045,8 @@
mFastTrackAvailMask(((1 << FastMixerState::sMaxFastTracks) - 1) & ~1),
mHwSupportsPause(false), mHwPaused(false), mFlushPending(false),
mLeftVolFloat(-1.0), mRightVolFloat(-1.0),
- mDownStreamPatch{}
+ mDownStreamPatch{},
+ mIsTimestampAdvancing(kMinimumTimeBetweenTimestampChecksNs)
{
snprintf(mThreadName, kThreadNameLength, "AudioOut_%X", id);
mNBLogWriter = audioFlinger->newWriter_l(kLogSize, mThreadName);
@@ -3146,10 +3152,7 @@
auto backInserter = std::back_inserter(metadata.tracks);
for (const sp<Track> &track : mActiveTracks) {
// No track is invalid as this is called after prepareTrack_l in the same critical section
- // Do not forward metadata for PatchTrack with unspecified stream type
- if (track->streamType() != AUDIO_STREAM_PATCH) {
- track->copyMetadataTo(backInserter);
- }
+ track->copyMetadataTo(backInserter);
}
sendMetadataToBackend_l(metadata);
}
@@ -5907,18 +5910,35 @@
return trackCount;
}
-bool AudioFlinger::PlaybackThread::checkRunningTimestamp()
+bool AudioFlinger::PlaybackThread::IsTimestampAdvancing::check(AudioStreamOut * output)
{
+ // Check the timestamp to see if it's advancing once every 150ms. If we check too frequently, we
+ // could falsely detect that the frame position has stalled due to underrun because we haven't
+ // given the Audio HAL enough time to update.
+ const nsecs_t nowNs = systemTime();
+ if (nowNs - mPreviousNs < mMinimumTimeBetweenChecksNs) {
+ return mLatchedValue;
+ }
+ mPreviousNs = nowNs;
+ mLatchedValue = false;
+ // Determine if the presentation position is still advancing.
uint64_t position = 0;
struct timespec unused;
- const status_t ret = mOutput->getPresentationPosition(&position, &unused);
+ const status_t ret = output->getPresentationPosition(&position, &unused);
if (ret == NO_ERROR) {
- if (position != mLastCheckedTimestampPosition) {
- mLastCheckedTimestampPosition = position;
- return true;
+ if (position != mPreviousPosition) {
+ mPreviousPosition = position;
+ mLatchedValue = true;
}
}
- return false;
+ return mLatchedValue;
+}
+
+void AudioFlinger::PlaybackThread::IsTimestampAdvancing::clear()
+{
+ mLatchedValue = true;
+ mPreviousPosition = 0;
+ mPreviousNs = 0;
}
// isTrackAllowed_l() must be called with ThreadBase::mLock held
@@ -6353,9 +6373,9 @@
// No buffers for this track. Give it a few chances to
// fill a buffer, then remove it from active list.
// Only consider last track started for mixer state control
+ bool isTimestampAdvancing = mIsTimestampAdvancing.check(mOutput);
if (--(track->mRetryCount) <= 0) {
- const bool running = checkRunningTimestamp();
- if (running) { // still running, give us more time.
+ if (isTimestampAdvancing) { // HAL is still playing audio, give us more time.
track->mRetryCount = kMaxTrackRetriesOffload;
} else {
ALOGV("BUFFER TIMEOUT: remove track(%d) from active list", trackId);
@@ -6936,9 +6956,9 @@
} else {
// No buffers for this track. Give it a few chances to
// fill a buffer, then remove it from active list.
+ bool isTimestampAdvancing = mIsTimestampAdvancing.check(mOutput);
if (--(track->mRetryCount) <= 0) {
- const bool running = checkRunningTimestamp();
- if (running) { // still running, give us more time.
+ if (isTimestampAdvancing) { // HAL is still playing audio, give us more time.
track->mRetryCount = kMaxTrackRetriesOffload;
} else {
ALOGV("OffloadThread: BUFFER TIMEOUT: remove track(%d) from active list",
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 2f85378..ad5617d 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -1172,7 +1172,7 @@
volatile int32_t mSuspended;
int64_t mBytesWritten;
- int64_t mFramesWritten; // not reset on standby
+ std::atomic<int64_t> mFramesWritten; // not reset on standby
int64_t mLastFramesWritten = -1; // track changes in timestamp
// server frames written.
int64_t mSuspendedFrames; // not reset on standby
@@ -1386,6 +1386,7 @@
virtual bool hasFastMixer() const = 0;
virtual FastTrackUnderruns getFastTrackUnderruns(size_t fastIndex __unused) const
{ FastTrackUnderruns dummy; return dummy; }
+ const std::atomic<int64_t>& framesWritten() const { return mFramesWritten; }
protected:
// accessed by both binder threads and within threadLoop(), lock on mutex needed
@@ -1403,13 +1404,38 @@
std::atomic_bool mCheckOutputStageEffects{};
- // A differential check on the timestamps to see if there is a change in the
- // timestamp frame position between the last call to checkRunningTimestamp.
- uint64_t mLastCheckedTimestampPosition = ~0LL;
- bool checkRunningTimestamp();
+ // Provides periodic checking for timestamp advancement for underrun detection.
+ class IsTimestampAdvancing {
+ public:
+ // The timestamp will not be checked any faster than the specified time.
+ IsTimestampAdvancing(nsecs_t minimumTimeBetweenChecksNs)
+ : mMinimumTimeBetweenChecksNs(minimumTimeBetweenChecksNs)
+ {
+ clear();
+ }
+ // Check if the presentation position has advanced in the last periodic time.
+ bool check(AudioStreamOut * output);
+ // Clear the internal state when the playback state changes for the output
+ // stream.
+ void clear();
+ private:
+ // The minimum time between timestamp checks.
+ const nsecs_t mMinimumTimeBetweenChecksNs;
+ // Add differential check on the timestamps to see if there is a change in the
+ // timestamp frame position between the last call to check.
+ uint64_t mPreviousPosition;
+ // The time at which the last check occurred, to ensure we don't check too
+ // frequently, giving the Audio HAL enough time to update its timestamps.
+ nsecs_t mPreviousNs;
+ // The valued is latched so we don't check timestamps too frequently.
+ bool mLatchedValue;
+ };
+ IsTimestampAdvancing mIsTimestampAdvancing;
- virtual void flushHw_l() { mLastCheckedTimestampPosition = ~0LL; }
+ virtual void flushHw_l() {
+ mIsTimestampAdvancing.clear();
+ }
};
class MixerThread : public PlaybackThread {
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 2677ab3..20bfbb0 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -397,6 +397,8 @@
int64_t mLogStartFrames = 0; // Timestamp frames at start()
double mLogLatencyMs = 0.; // Track the last log latency
+ bool mLogForceVolumeUpdate = true; // force volume update to TrackMetrics.
+
TrackMetrics mTrackMetrics;
bool mServerLatencySupported = false;
diff --git a/services/audioflinger/TrackMetrics.h b/services/audioflinger/TrackMetrics.h
index 30d69ab..6fc70d6 100644
--- a/services/audioflinger/TrackMetrics.h
+++ b/services/audioflinger/TrackMetrics.h
@@ -64,7 +64,6 @@
AMEDIAMETRICS_PROP_EVENT_VALUE_BEGINAUDIOINTERVALGROUP, devices.c_str());
}
++mIntervalCount;
- mIntervalStartTimeNs = systemTime();
}
void logConstructor(pid_t creatorPid, uid_t creatorUid, int32_t internalTrackId,
@@ -90,11 +89,9 @@
// Called when we are removed from the Thread.
void logEndInterval() {
std::lock_guard l(mLock);
- if (mIntervalStartTimeNs != 0) {
- const int64_t elapsedTimeNs = systemTime() - mIntervalStartTimeNs;
- mIntervalStartTimeNs = 0;
- mCumulativeTimeNs += elapsedTimeNs;
- mDeviceTimeNs += elapsedTimeNs;
+ if (mLastVolumeChangeTimeNs != 0) {
+ logVolume_l(mVolume); // flush out the last volume.
+ mLastVolumeChangeTimeNs = 0;
}
}
@@ -133,20 +130,8 @@
// may be called multiple times during an interval
void logVolume(float volume) {
- const int64_t timeNs = systemTime();
std::lock_guard l(mLock);
- if (mStartVolumeTimeNs == 0) {
- mDeviceVolume = mVolume = volume;
- mLastVolumeChangeTimeNs = mStartVolumeTimeNs = timeNs;
- updateMinMaxVolume(0, mVolume);
- return;
- }
- const int64_t durationNs = timeNs - mLastVolumeChangeTimeNs;
- updateMinMaxVolume(durationNs, mVolume);
- mDeviceVolume = (mDeviceVolume * (mLastVolumeChangeTimeNs - mStartVolumeTimeNs) +
- mVolume * durationNs) / (timeNs - mStartVolumeTimeNs);
- mVolume = volume;
- mLastVolumeChangeTimeNs = timeNs;
+ logVolume_l(volume);
}
// Use absolute numbers returned by AudioTrackShared.
@@ -158,6 +143,7 @@
}
private:
+
// no lock required - all arguments and constants.
void deliverDeviceMetrics(const char *eventName, const char *devices) const {
mediametrics::LogItem(mMetricsId)
@@ -167,6 +153,23 @@
.record();
}
+ void logVolume_l(float volume) REQUIRES(mLock) {
+ const int64_t timeNs = systemTime();
+ const int64_t durationNs = mLastVolumeChangeTimeNs == 0
+ ? 0 : timeNs - mLastVolumeChangeTimeNs;
+ if (durationNs > 0) {
+ // See West's algorithm for weighted averages
+ // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
+ mDeviceVolume += (mVolume - mDeviceVolume) * durationNs
+ / (durationNs + mDeviceTimeNs);
+ mDeviceTimeNs += durationNs;
+ mCumulativeTimeNs += durationNs;
+ }
+ updateMinMaxVolume(durationNs, mVolume); // always update.
+ mVolume = volume;
+ mLastVolumeChangeTimeNs = timeNs;
+ }
+
void deliverCumulativeMetrics(const char *eventName) const REQUIRES(mLock) {
if (mIntervalCount > 0) {
mediametrics::LogItem item(mMetricsId);
@@ -199,14 +202,12 @@
// mDevices is not reset by resetIntervalGroupMetrics.
mIntervalCount = 0;
- mIntervalStartTimeNs = 0;
// mCumulativeTimeNs is not reset by resetIntervalGroupMetrics.
mDeviceTimeNs = 0;
mVolume = 0.f;
mDeviceVolume = 0.f;
- mStartVolumeTimeNs = 0;
- mLastVolumeChangeTimeNs = 0;
+ mLastVolumeChangeTimeNs = 0; // last time volume logged, cleared on endInterval
mMinVolume = AMEDIAMETRICS_INITIAL_MIN_VOLUME;
mMaxVolume = AMEDIAMETRICS_INITIAL_MAX_VOLUME;
mMinVolumeDurationNs = 0;
@@ -230,14 +231,12 @@
// Number of intervals and playing time
int32_t mIntervalCount GUARDED_BY(mLock) = 0;
- int64_t mIntervalStartTimeNs GUARDED_BY(mLock) = 0;
- int64_t mCumulativeTimeNs GUARDED_BY(mLock) = 0;
- int64_t mDeviceTimeNs GUARDED_BY(mLock) = 0;
+ int64_t mCumulativeTimeNs GUARDED_BY(mLock) = 0; // total time.
+ int64_t mDeviceTimeNs GUARDED_BY(mLock) = 0; // time on device.
// Average volume
- double mVolume GUARDED_BY(mLock) = 0.f;
- double mDeviceVolume GUARDED_BY(mLock) = 0.f;
- int64_t mStartVolumeTimeNs GUARDED_BY(mLock) = 0;
+ double mVolume GUARDED_BY(mLock) = 0.f; // last set volume.
+ double mDeviceVolume GUARDED_BY(mLock) = 0.f; // running average volume.
int64_t mLastVolumeChangeTimeNs GUARDED_BY(mLock) = 0;
// Min/Max volume
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 6135020..95ca855 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -1094,12 +1094,22 @@
__func__, mId, (int)mThreadIoHandle);
}
- // states to reset position info for non-offloaded/direct tracks
- if (!isOffloaded() && !isDirect()
+ PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
+
+ // states to reset position info for pcm tracks
+ if (audio_is_linear_pcm(mFormat)
&& (state == IDLE || state == STOPPED || state == FLUSHED)) {
mFrameMap.reset();
+
+ if (!isFastTrack() && (isDirect() || isOffloaded())) {
+ // Start point of track -> sink frame map. If the HAL returns a
+ // frame position smaller than the first written frame in
+ // updateTrackFrameInfo, the timestamp can be interpolated
+ // instead of using a larger value.
+ mFrameMap.push(mAudioTrackServerProxy->framesReleased(),
+ playbackThread->framesWritten());
+ }
}
- PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
if (isFastTrack()) {
// refresh fast track underruns on start because that field is never cleared
// by the fast mixer; furthermore, the same track can be recycled, i.e. start
@@ -1123,6 +1133,7 @@
.mPosition[ExtendedTimestamp::LOCATION_KERNEL];
mLogLatencyMs = 0.;
}
+ mLogForceVolumeUpdate = true; // at least one volume logged for metrics when starting.
if (status == NO_ERROR || status == ALREADY_EXISTS) {
// for streaming tracks, remove the buffer read stop limit.
@@ -1394,12 +1405,21 @@
if (mFinalVolume != volume) { // Compare to an epsilon if too many meaningless updates
mFinalVolume = volume;
setMetadataHasChanged();
- mTrackMetrics.logVolume(volume);
+ mLogForceVolumeUpdate = true;
+ }
+ if (mLogForceVolumeUpdate) {
+ mLogForceVolumeUpdate = false;
+ mTrackMetrics.logVolume(mFinalVolume);
}
}
void AudioFlinger::PlaybackThread::Track::copyMetadataTo(MetadataInserter& backInserter) const
{
+ // Do not forward metadata for PatchTrack with unspecified stream type
+ if (mStreamType == AUDIO_STREAM_PATCH) {
+ return;
+ }
+
playback_track_metadata_v7_t metadata;
metadata.base = {
.usage = mAttr.usage,
diff --git a/services/audiopolicy/config/bluetooth_with_le_audio_policy_configuration_7_0.xml b/services/audiopolicy/config/bluetooth_with_le_audio_policy_configuration_7_0.xml
index e7908eb..c453dea 100644
--- a/services/audiopolicy/config/bluetooth_with_le_audio_policy_configuration_7_0.xml
+++ b/services/audiopolicy/config/bluetooth_with_le_audio_policy_configuration_7_0.xml
@@ -12,6 +12,7 @@
</mixPort>
<!-- Le Audio Audio Ports -->
<mixPort name="le audio output" role="source" />
+ <mixPort name="le audio broadcast output" role="source" />
<mixPort name="le audio input" role="sink">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="8000 16000 24000 32000 44100 48000"
@@ -47,6 +48,7 @@
<devicePort tagName="BLE Headset Out" type="AUDIO_DEVICE_OUT_BLE_HEADSET" role="sink"/>
<devicePort tagName="BLE Speaker Out" type="AUDIO_DEVICE_OUT_BLE_SPEAKER" role="sink"/>
<devicePort tagName="BLE Headset In" type="AUDIO_DEVICE_IN_BLE_HEADSET" role="source"/>
+ <devicePort tagName="BLE Broadcast Out" type="AUDIO_DEVICE_OUT_BLE_BROADCAST" role="sink"/>
</devicePorts>
<routes>
<route type="mix" sink="BT A2DP Out"
@@ -63,5 +65,7 @@
sources="BLE Headset In"/>
<route type="mix" sink="BLE Speaker Out"
sources="le audio output"/>
+ <route type="mix" sink="BLE Broadcast Out"
+ sources="le audio broadcast output"/>
</routes>
</module>
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index d4d514d..08d6453 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -195,6 +195,12 @@
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP, AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER, }));
}
+ // If connected to a dock, never use the device speaker for calls
+ if (!availableOutputDevices.getDevicesFromTypes({AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET})
+ .isEmpty()) {
+ availableOutputDevices.remove(
+ availableOutputDevices.getDevicesFromTypes({AUDIO_DEVICE_OUT_SPEAKER}));
+ }
} break;
case STRATEGY_ACCESSIBILITY: {
// do not route accessibility prompts to a digital output currently configured with a
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 83de3e2..8586f6f 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -1884,7 +1884,8 @@
// (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
+ // 5: the output with the highest sampling rate if the requested sample rate is
+ // greater than default sampling rate
// 6: the output with the highest number of requested performance flags
// 7: the output with the bit depth the closest to the requested one
// 8: the primary output
@@ -1944,8 +1945,7 @@
}
// sampling rate match
- if (samplingRate > SAMPLE_RATE_HZ_DEFAULT &&
- samplingRate <= outputDesc->getSamplingRate()) {
+ if (samplingRate > SAMPLE_RATE_HZ_DEFAULT) {
currentMatchCriteria[4] = outputDesc->getSamplingRate();
}
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index 50a536b..4f3ea6c 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -1342,7 +1342,9 @@
} else {
getIAudioPolicyServiceStatistics().event(code, elapsedMs);
}
- });
+ }, mediautils::TimeCheck::kDefaultTimeoutDuration,
+ mediautils::TimeCheck::kDefaultSecondChanceDuration,
+ true /* crashOnTimeout */);
switch (code) {
case SHELL_COMMAND_TRANSACTION: {
diff --git a/services/audiopolicy/service/SpatializerPoseController.cpp b/services/audiopolicy/service/SpatializerPoseController.cpp
index 5e9a0e1..9735479 100644
--- a/services/audiopolicy/service/SpatializerPoseController.cpp
+++ b/services/audiopolicy/service/SpatializerPoseController.cpp
@@ -67,13 +67,13 @@
// Screen is considered to be unstable (not still) if it has moved significantly within the last
// time window of this duration.
-constexpr auto kScreenStillnessWindowDuration = 3s;
+constexpr auto kScreenStillnessWindowDuration = 750ms;
// Screen is considered to have moved significantly if translated by this much (in meter, approx).
constexpr float kScreenStillnessTranslationThreshold = 0.1f;
// Screen is considered to have moved significantly if rotated by this much (in radians, approx).
-constexpr float kScreenStillnessRotationThreshold = 7.0f / 180 * M_PI;
+constexpr float kScreenStillnessRotationThreshold = 15.0f / 180 * M_PI;
// Time units for system clock ticks. This is what the Sensor Framework timestamps represent and
// what we use for pose filtering.
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 80410ab..a106cd6 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -1698,7 +1698,13 @@
return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, msg.string());
}
- if (CameraServiceProxyWrapper::isCameraDisabled()) {
+ userid_t clientUserId = multiuser_get_user_id(clientUid);
+ int callingUid = CameraThreadState::getCallingUid();
+ if (clientUid == USE_CALLING_UID) {
+ clientUserId = multiuser_get_user_id(callingUid);
+ }
+
+ if (CameraServiceProxyWrapper::isCameraDisabled(clientUserId)) {
String8 msg =
String8::format("Camera disabled by device policy");
ALOGE("%s: %s", __FUNCTION__, msg.string());
diff --git a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
index 69175cc..dae5eea 100644
--- a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
+++ b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
@@ -262,11 +262,11 @@
sessionStats->onClose(latencyMs);
}
-bool CameraServiceProxyWrapper::isCameraDisabled() {
+bool CameraServiceProxyWrapper::isCameraDisabled(int userId) {
sp<ICameraServiceProxy> proxyBinder = getCameraServiceProxy();
if (proxyBinder == nullptr) return true;
bool ret = false;
- auto status = proxyBinder->isCameraDisabled(&ret);
+ auto status = proxyBinder->isCameraDisabled(userId, &ret);
if (!status.isOk()) {
ALOGE("%s: Failed during camera disabled query: %s", __FUNCTION__,
status.exceptionMessage().c_str());
diff --git a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
index e34a8f0..eb818d1 100644
--- a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
+++ b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
@@ -97,7 +97,7 @@
static int getRotateAndCropOverride(String16 packageName, int lensFacing, int userId);
// Detect if the camera is disabled by device policy.
- static bool isCameraDisabled();
+ static bool isCameraDisabled(int userId);
};
} // android
diff --git a/services/oboeservice/AAudioServiceEndpointMMAP.cpp b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
index 3f18b95..ea817ab 100644
--- a/services/oboeservice/AAudioServiceEndpointMMAP.cpp
+++ b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
@@ -71,6 +71,8 @@
aaudio_result_t AAudioServiceEndpointMMAP::open(const aaudio::AAudioStreamRequest &request) {
aaudio_result_t result = AAUDIO_OK;
copyFrom(request.getConstantConfiguration());
+ mRequestedDeviceId = getDeviceId();
+
mMmapClient.attributionSource = request.getAttributionSource();
// TODO b/182392769: use attribution source util
mMmapClient.attributionSource.uid = VALUE_OR_FATAL(
@@ -115,7 +117,7 @@
const audio_attributes_t attributes = getAudioAttributesFrom(this);
- mRequestedDeviceId = deviceId = getDeviceId();
+ deviceId = mRequestedDeviceId;
// Fill in config
config.format = audioFormat;
@@ -151,6 +153,10 @@
audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
// Open HAL stream. Set mMmapStream
+ ALOGD("%s trying to open MMAP stream with format=%#x, "
+ "sample_rate=%u, channel_mask=%#x, device=%d",
+ __func__, config.format, config.sample_rate,
+ config.channel_mask, deviceId);
status_t status = MmapStreamInterface::openMmapStream(streamDirection,
&attributes,
&config,
@@ -221,6 +227,9 @@
error:
close();
+ // restore original requests
+ setDeviceId(mRequestedDeviceId);
+ setSessionId(requestedSessionId);
return result;
}