Merge "Include header for property_get_int32() prototype"
diff --git a/include/private/media/VideoFrame.h b/include/private/media/VideoFrame.h
index 712f118..16e794a 100644
--- a/include/private/media/VideoFrame.h
+++ b/include/private/media/VideoFrame.h
@@ -41,7 +41,7 @@
uint32_t angle, uint32_t bpp, bool hasData, size_t iccSize):
mWidth(width), mHeight(height),
mDisplayWidth(displayWidth), mDisplayHeight(displayHeight),
- mTileWidth(tileWidth), mTileHeight(tileHeight),
+ mTileWidth(tileWidth), mTileHeight(tileHeight), mDurationUs(0),
mRotationAngle(angle), mBytesPerPixel(bpp), mRowBytes(bpp * width),
mSize(hasData ? (bpp * width * height) : 0),
mIccSize(iccSize), mReserved(0) {
@@ -78,6 +78,7 @@
uint32_t mDisplayHeight; // Display height before rotation
uint32_t mTileWidth; // Tile width (0 if image doesn't have grid)
uint32_t mTileHeight; // Tile height (0 if image doesn't have grid)
+ int64_t mDurationUs; // Frame duration in microseconds
int32_t mRotationAngle; // Rotation angle, clockwise, should be multiple of 90
uint32_t mBytesPerPixel; // Number of bytes per pixel
uint32_t mRowBytes; // Number of bytes per row before rotation
diff --git a/media/libheif/HeifDecoderImpl.cpp b/media/libheif/HeifDecoderImpl.cpp
index a7c0f84..bad4210 100644
--- a/media/libheif/HeifDecoderImpl.cpp
+++ b/media/libheif/HeifDecoderImpl.cpp
@@ -44,8 +44,7 @@
info->mHeight = videoFrame->mHeight;
info->mRotationAngle = videoFrame->mRotationAngle;
info->mBytesPerPixel = videoFrame->mBytesPerPixel;
- // TODO: retrieve per-frame duration from extractor/metadataretriever.
- info->mDurationUs = 33333;
+ info->mDurationUs = videoFrame->mDurationUs;
if (videoFrame->mIccSize > 0) {
info->mIccData.assign(
videoFrame->getFlattenedIccData(),
diff --git a/media/libstagefright/FrameDecoder.cpp b/media/libstagefright/FrameDecoder.cpp
index c6ec6de..f99dd1c 100644
--- a/media/libstagefright/FrameDecoder.cpp
+++ b/media/libstagefright/FrameDecoder.cpp
@@ -43,6 +43,7 @@
static const int64_t kBufferTimeOutUs = 10000LL; // 10 msec
static const size_t kRetryCount = 50; // must be >0
+static const int64_t kDefaultSampleDurationUs = 33333LL; // 33ms
sp<IMemory> allocVideoFrame(const sp<MetaData>& trackMeta,
int32_t width, int32_t height, int32_t tileWidth, int32_t tileHeight,
@@ -199,7 +200,19 @@
tileWidth = tileHeight = 0;
}
}
- return allocMetaFrame(trackMeta, width, height, tileWidth, tileHeight, dstBpp);
+
+ sp<IMemory> metaMem = allocMetaFrame(trackMeta, width, height, tileWidth, tileHeight, dstBpp);
+
+ // try to fill sequence meta's duration based on average frame rate,
+ // default to 33ms if frame rate is unavailable.
+ int32_t frameRate;
+ VideoFrame* meta = static_cast<VideoFrame*>(metaMem->pointer());
+ if (trackMeta->findInt32(kKeyFrameRate, &frameRate) && frameRate > 0) {
+ meta->mDurationUs = 1000000LL / frameRate;
+ } else {
+ meta->mDurationUs = kDefaultSampleDurationUs;
+ }
+ return metaMem;
}
FrameDecoder::FrameDecoder(
@@ -443,7 +456,8 @@
mFrame(NULL),
mIsAvcOrHevc(false),
mSeekMode(MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC),
- mTargetTimeUs(-1LL) {
+ mTargetTimeUs(-1LL),
+ mDefaultSampleDurationUs(0) {
}
sp<AMessage> VideoFrameDecoder::onGetFormatAndSeekOptions(
@@ -506,6 +520,13 @@
}
}
+ int32_t frameRate;
+ if (trackMeta()->findInt32(kKeyFrameRate, &frameRate) && frameRate > 0) {
+ mDefaultSampleDurationUs = 1000000LL / frameRate;
+ } else {
+ mDefaultSampleDurationUs = kDefaultSampleDurationUs;
+ }
+
return videoFormat;
}
@@ -526,6 +547,12 @@
// option, in which case we need to actually decode to targetTimeUs.
*flags |= MediaCodec::BUFFER_FLAG_EOS;
}
+ int64_t durationUs;
+ if (sampleMeta.findInt64(kKeyDuration, &durationUs)) {
+ mSampleDurations.push_back(durationUs);
+ } else {
+ mSampleDurations.push_back(mDefaultSampleDurationUs);
+ }
return OK;
}
@@ -533,6 +560,11 @@
const sp<MediaCodecBuffer> &videoFrameBuffer,
const sp<AMessage> &outputFormat,
int64_t timeUs, bool *done) {
+ int64_t durationUs = mDefaultSampleDurationUs;
+ if (!mSampleDurations.empty()) {
+ durationUs = *mSampleDurations.begin();
+ mSampleDurations.erase(mSampleDurations.begin());
+ }
bool shouldOutput = (mTargetTimeUs < 0LL) || (timeUs >= mTargetTimeUs);
// If this is not the target frame, skip color convert.
@@ -587,6 +619,8 @@
setFrame(frameMem);
}
+ mFrame->mDurationUs = durationUs;
+
if (mSurfaceControl != nullptr) {
return captureSurfaceControl();
}
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index ae0fa3c..eceb84e 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -48,6 +48,7 @@
#include <media/stagefright/foundation/avc_utils.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/ACodec.h>
+#include <media/stagefright/BatteryChecker.h>
#include <media/stagefright/BufferProducerWrapper.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecList.h>
@@ -116,7 +117,6 @@
static const int kMaxRetry = 2;
static const int kMaxReclaimWaitTimeInUs = 500000; // 0.5s
static const int kNumBuffersAlign = 16;
-static const int kBatteryStatsTimeoutUs = 3000000ll; // 3 seconds
////////////////////////////////////////////////////////////////////////////////
@@ -539,10 +539,7 @@
mHaveInputSurface(false),
mHavePendingInputBuffers(false),
mCpuBoostRequested(false),
- mLatencyUnknown(0),
- mLastActivityTimeUs(-1ll),
- mBatteryStatNotified(false),
- mBatteryCheckerGeneration(0) {
+ mLatencyUnknown(0) {
if (uid == kNoUid) {
mUid = IPCThreadState::self()->getCallingUid();
} else {
@@ -755,7 +752,11 @@
return;
}
- scheduleBatteryCheckerIfNeeded();
+ if (mBatteryChecker != nullptr) {
+ mBatteryChecker->onCodecActivity([this] () {
+ addResource(MediaResource::kBattery, MediaResource::kVideoCodec, 1);
+ });
+ }
const int64_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC);
BufferFlightTiming_t startdata = { presentationUs, nowNs };
@@ -791,7 +792,11 @@
return;
}
- scheduleBatteryCheckerIfNeeded();
+ if (mBatteryChecker != nullptr) {
+ mBatteryChecker->onCodecActivity([this] () {
+ addResource(MediaResource::kBattery, MediaResource::kVideoCodec, 1);
+ });
+ }
BufferFlightTiming_t startdata;
bool valid = false;
@@ -981,6 +986,10 @@
mAnalyticsItem->setCString(kCodecMode, mIsVideo ? kCodecModeVideo : kCodecModeAudio);
}
+ if (mIsVideo) {
+ mBatteryChecker = new BatteryChecker(new AMessage(kWhatCheckBatteryStats, this));
+ }
+
status_t err;
Vector<MediaResource> resources;
MediaResource::Type type =
@@ -1706,19 +1715,27 @@
}
}
-void MediaCodec::scheduleBatteryCheckerIfNeeded() {
- if (!mIsVideo || !isExecuting()) {
+BatteryChecker::BatteryChecker(const sp<AMessage> &msg, int64_t timeoutUs)
+ : mTimeoutUs(timeoutUs)
+ , mLastActivityTimeUs(-1ll)
+ , mBatteryStatNotified(false)
+ , mBatteryCheckerGeneration(0)
+ , mIsExecuting(false)
+ , mBatteryCheckerMsg(msg) {}
+
+void BatteryChecker::onCodecActivity(std::function<void()> batteryOnCb) {
+ if (!isExecuting()) {
// ignore if not executing
return;
}
if (!mBatteryStatNotified) {
- addResource(MediaResource::kBattery, MediaResource::kVideoCodec, 1);
+ batteryOnCb();
mBatteryStatNotified = true;
- sp<AMessage> msg = new AMessage(kWhatCheckBatteryStats, this);
+ sp<AMessage> msg = mBatteryCheckerMsg->dup();
msg->setInt32("generation", mBatteryCheckerGeneration);
// post checker and clear last activity time
- msg->post(kBatteryStatsTimeoutUs);
+ msg->post(mTimeoutUs);
mLastActivityTimeUs = -1ll;
} else {
// update last activity time
@@ -1726,7 +1743,8 @@
}
}
-void MediaCodec::onBatteryChecker(const sp<AMessage> &msg) {
+void BatteryChecker::onCheckBatteryTimer(
+ const sp<AMessage> &msg, std::function<void()> batteryOffCb) {
// ignore if this checker already expired because the client resource was removed
int32_t generation;
if (!msg->findInt32("generation", &generation)
@@ -1736,15 +1754,20 @@
if (mLastActivityTimeUs < 0ll) {
// timed out inactive, do not repost checker
- removeResource(MediaResource::kBattery, MediaResource::kVideoCodec, 1);
+ batteryOffCb();
mBatteryStatNotified = false;
} else {
// repost checker and clear last activity time
- msg->post(kBatteryStatsTimeoutUs + mLastActivityTimeUs - ALooper::GetNowUs());
+ msg->post(mTimeoutUs + mLastActivityTimeUs - ALooper::GetNowUs());
mLastActivityTimeUs = -1ll;
}
}
+void BatteryChecker::onClientRemoved() {
+ mBatteryStatNotified = false;
+ mBatteryCheckerGeneration++;
+}
+
////////////////////////////////////////////////////////////////////////////////
void MediaCodec::cancelPendingDequeueOperations() {
@@ -2382,8 +2405,10 @@
mFlags &= ~kFlagIsComponentAllocated;
// off since we're removing all resources including the battery on
- mBatteryStatNotified = false;
- mBatteryCheckerGeneration++;
+ if (mBatteryChecker != nullptr) {
+ mBatteryChecker->onClientRemoved();
+ }
+
mResourceManagerService->removeClient(getId(mResourceManagerClient));
(new AMessage)->postReply(mReplyID);
@@ -3097,7 +3122,11 @@
case kWhatCheckBatteryStats:
{
- onBatteryChecker(msg);
+ if (mBatteryChecker != nullptr) {
+ mBatteryChecker->onCheckBatteryTimer(msg, [this] () {
+ removeResource(MediaResource::kBattery, MediaResource::kVideoCodec, 1);
+ });
+ }
break;
}
@@ -3197,6 +3226,10 @@
mState = newState;
+ if (mBatteryChecker != nullptr) {
+ mBatteryChecker->setExecuting(isExecuting());
+ }
+
cancelPendingDequeueOperations();
}
diff --git a/media/libstagefright/TEST_MAPPING b/media/libstagefright/TEST_MAPPING
index 96818eb..c1b270c 100644
--- a/media/libstagefright/TEST_MAPPING
+++ b/media/libstagefright/TEST_MAPPING
@@ -7,6 +7,9 @@
"include-annotation": "android.platform.test.annotations.RequiresDevice"
}
]
+ },
+ {
+ "name": "BatteryChecker_test"
}
]
}
diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
index cf91405..4711315 100644
--- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp
+++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
@@ -31,9 +31,14 @@
namespace android {
-static int ALIGN(int x, int y) {
- // y must be a power of 2.
- return (x + y - 1) & ~(y - 1);
+inline void initDstYUV(
+ const android_ycbcr &ycbcr, int32_t cropTop, int32_t cropLeft,
+ uint8_t **dst_y, uint8_t **dst_u, uint8_t **dst_v) {
+ *dst_y = (uint8_t *)ycbcr.y + cropTop * ycbcr.ystride + cropLeft;
+
+ int32_t c_offset = (cropTop / 2) * ycbcr.cstride + cropLeft / 2;
+ *dst_v = (uint8_t *)ycbcr.cr + c_offset;
+ *dst_u = (uint8_t *)ycbcr.cb + c_offset;
}
SoftwareRenderer::SoftwareRenderer(
@@ -300,20 +305,14 @@
const uint8_t *src_u = (const uint8_t *)data + mStride * mHeight + mCropTop * mStride / 4;
const uint8_t *src_v = (const uint8_t *)src_u + mStride * mHeight / 4;
- uint8_t *dst_y = (uint8_t *)ycbcr.y;
- uint8_t *dst_v = (uint8_t *)ycbcr.cr;
- uint8_t *dst_u = (uint8_t *)ycbcr.cb;
- size_t dst_c_stride = ALIGN(buf->stride / 2, 16);
-
- dst_y += mCropTop * buf->stride + mCropLeft;
- dst_v += (mCropTop/2) * dst_c_stride + mCropLeft/2;
- dst_u += (mCropTop/2) * dst_c_stride + mCropLeft/2;
+ uint8_t *dst_y, *dst_u, *dst_v;
+ initDstYUV(ycbcr, mCropTop, mCropLeft, &dst_y, &dst_u, &dst_v);
for (int y = 0; y < mCropHeight; ++y) {
memcpy(dst_y, src_y, mCropWidth);
src_y += mStride;
- dst_y += buf->stride;
+ dst_y += ycbcr.ystride;
}
for (int y = 0; y < (mCropHeight + 1) / 2; ++y) {
@@ -322,22 +321,16 @@
src_u += mStride / 2;
src_v += mStride / 2;
- dst_u += dst_c_stride;
- dst_v += dst_c_stride;
+ dst_u += ycbcr.cstride;
+ dst_v += ycbcr.cstride;
}
} else if (mColorFormat == OMX_COLOR_FormatYUV420Planar16) {
const uint8_t *src_y = (const uint8_t *)data + mCropTop * mStride + mCropLeft * 2;
const uint8_t *src_u = (const uint8_t *)data + mStride * mHeight + mCropTop * mStride / 4;
const uint8_t *src_v = (const uint8_t *)src_u + mStride * mHeight / 4;
- uint8_t *dst_y = (uint8_t *)ycbcr.y;
- uint8_t *dst_v = (uint8_t *)ycbcr.cr;
- uint8_t *dst_u = (uint8_t *)ycbcr.cb;
- size_t dst_c_stride = ALIGN(buf->stride / 2, 16);
-
- dst_y += mCropTop * buf->stride + mCropLeft;
- dst_v += (mCropTop / 2) * dst_c_stride + mCropLeft / 2;
- dst_u += (mCropTop / 2) * dst_c_stride + mCropLeft / 2;
+ uint8_t *dst_y, *dst_u, *dst_v;
+ initDstYUV(ycbcr, mCropTop, mCropLeft, &dst_y, &dst_u, &dst_v);
for (int y = 0; y < mCropHeight; ++y) {
for (int x = 0; x < mCropWidth; ++x) {
@@ -345,7 +338,7 @@
}
src_y += mStride;
- dst_y += buf->stride;
+ dst_y += ycbcr.ystride;
}
for (int y = 0; y < (mCropHeight + 1) / 2; ++y) {
@@ -356,8 +349,8 @@
src_u += mStride / 2;
src_v += mStride / 2;
- dst_u += dst_c_stride;
- dst_v += dst_c_stride;
+ dst_u += ycbcr.cstride;
+ dst_v += ycbcr.cstride;
}
} else if (mColorFormat == OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
|| mColorFormat == OMX_COLOR_FormatYUV420SemiPlanar) {
@@ -368,20 +361,14 @@
src_y += mCropLeft + mCropTop * mWidth;
src_uv += (mCropLeft + mCropTop * mWidth) / 2;
- uint8_t *dst_y = (uint8_t *)ycbcr.y;
- uint8_t *dst_v = (uint8_t *)ycbcr.cr;
- uint8_t *dst_u = (uint8_t *)ycbcr.cb;
- size_t dst_c_stride = ALIGN(buf->stride / 2, 16);
-
- dst_y += mCropTop * buf->stride + mCropLeft;
- dst_v += (mCropTop/2) * dst_c_stride + mCropLeft/2;
- dst_u += (mCropTop/2) * dst_c_stride + mCropLeft/2;
+ uint8_t *dst_y, *dst_u, *dst_v;
+ initDstYUV(ycbcr, mCropTop, mCropLeft, &dst_y, &dst_u, &dst_v);
for (int y = 0; y < mCropHeight; ++y) {
memcpy(dst_y, src_y, mCropWidth);
src_y += mWidth;
- dst_y += buf->stride;
+ dst_y += ycbcr.ystride;
}
for (int y = 0; y < (mCropHeight + 1) / 2; ++y) {
@@ -392,8 +379,8 @@
}
src_uv += mWidth;
- dst_u += dst_c_stride;
- dst_v += dst_c_stride;
+ dst_u += ycbcr.cstride;
+ dst_v += ycbcr.cstride;
}
} else if (mColorFormat == OMX_COLOR_Format24bitRGB888) {
uint8_t* srcPtr = (uint8_t*)data + mWidth * mCropTop * 3 + mCropLeft * 3;
diff --git a/media/libstagefright/include/FrameDecoder.h b/media/libstagefright/include/FrameDecoder.h
index 1af6276..8e42fcf 100644
--- a/media/libstagefright/include/FrameDecoder.h
+++ b/media/libstagefright/include/FrameDecoder.h
@@ -139,6 +139,8 @@
bool mIsAvcOrHevc;
MediaSource::ReadOptions::SeekMode mSeekMode;
int64_t mTargetTimeUs;
+ List<int64_t> mSampleDurations;
+ int64_t mDefaultSampleDurationUs;
sp<Surface> initSurfaceControl();
status_t captureSurfaceControl();
diff --git a/media/libstagefright/include/media/stagefright/BatteryChecker.h b/media/libstagefright/include/media/stagefright/BatteryChecker.h
new file mode 100644
index 0000000..2ec4ac0
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/BatteryChecker.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BATTERY_CHECKER_H_
+#define BATTERY_CHECKER_H_
+
+#include <media/stagefright/foundation/AMessage.h>
+
+namespace android {
+
+struct BatteryChecker : public RefBase {
+ BatteryChecker(const sp<AMessage> &msg, int64_t timeout = 3000000ll);
+
+ void setExecuting(bool executing) { mIsExecuting = executing; }
+ void onCodecActivity(std::function<void()> batteryOnCb);
+ void onCheckBatteryTimer(const sp<AMessage>& msg, std::function<void()> batteryOffCb);
+ void onClientRemoved();
+
+private:
+ const int64_t mTimeoutUs;
+ int64_t mLastActivityTimeUs;
+ bool mBatteryStatNotified;
+ int32_t mBatteryCheckerGeneration;
+ bool mIsExecuting;
+ sp<AMessage> mBatteryCheckerMsg;
+
+ bool isExecuting() { return mIsExecuting; }
+
+ DISALLOW_EVIL_CONSTRUCTORS(BatteryChecker);
+};
+
+} // namespace android
+
+#endif // BATTERY_CHECKER_H_
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 462eb84e..cd30347 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -36,6 +36,7 @@
struct AMessage;
struct AReplyToken;
struct AString;
+struct BatteryChecker;
class BufferChannelBase;
struct CodecBase;
class IBatteryStats;
@@ -463,11 +464,7 @@
Mutex mLatencyLock;
int64_t mLatencyUnknown; // buffers for which we couldn't calculate latency
- int64_t mLastActivityTimeUs;
- bool mBatteryStatNotified;
- int32_t mBatteryCheckerGeneration;
- void onBatteryChecker(const sp<AMessage>& msg);
- void scheduleBatteryCheckerIfNeeded();
+ sp<BatteryChecker> mBatteryChecker;
void statsBufferSent(int64_t presentationUs);
void statsBufferReceived(int64_t presentationUs);
diff --git a/media/libstagefright/tests/Android.bp b/media/libstagefright/tests/Android.bp
index be10fdc..a7f94c1 100644
--- a/media/libstagefright/tests/Android.bp
+++ b/media/libstagefright/tests/Android.bp
@@ -27,3 +27,21 @@
"-Wall",
],
}
+
+cc_test {
+ name: "BatteryChecker_test",
+ srcs: ["BatteryChecker_test.cpp"],
+ test_suites: ["device-tests"],
+
+ shared_libs: [
+ "libstagefright",
+ "libstagefright_foundation",
+ "libutils",
+ "liblog",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
\ No newline at end of file
diff --git a/media/libstagefright/tests/BatteryChecker_test.cpp b/media/libstagefright/tests/BatteryChecker_test.cpp
new file mode 100644
index 0000000..0c5ee9b
--- /dev/null
+++ b/media/libstagefright/tests/BatteryChecker_test.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2019 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_NDEBUG 0
+#define LOG_TAG "BatteryChecker_test"
+#include <utils/Log.h>
+
+#include <gtest/gtest.h>
+
+#include <media/stagefright/BatteryChecker.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AHandler.h>
+
+#include <vector>
+
+namespace android {
+
+static const int kBatteryTimeoutUs = 1000000ll; // 1 seconds
+static const int kTestMarginUs = 50000ll; // 50ms
+static const int kWaitStatusChangeUs = kBatteryTimeoutUs + kTestMarginUs;
+static const int kSparseFrameIntervalUs = kBatteryTimeoutUs - kTestMarginUs;
+
+class BatteryCheckerTestHandler : public AHandler {
+ enum EventType {
+ // Events simulating MediaCodec
+ kWhatStart = 0, // codec entering executing state
+ kWhatStop, // codec exiting executing state
+ kWhatActivity, // codec queue input or dequeue output
+ kWhatReleased, // codec released
+ kWhatCheckpoint, // test checkpoing with expected values on On/Off
+
+ // Message for battery checker monitor (not for testing through runTest())
+ kWhatBatteryChecker,
+ };
+
+ struct Operation {
+ int32_t event;
+ int64_t delay = 0;
+ uint32_t repeatCount = 0;
+ int32_t expectedOnCounter = 0;
+ int32_t expectedOffCounter = 0;
+ };
+
+ std::vector<Operation> mOps;
+ sp<BatteryChecker> mBatteryChecker;
+ int32_t mOnCounter;
+ int32_t mOffCounter;
+ Condition mDone;
+ Mutex mLock;
+
+ BatteryCheckerTestHandler() : mOnCounter(0), mOffCounter(0) {}
+
+ void runTest(const std::vector<Operation> &ops, int64_t timeoutUs) {
+ mOps = ops;
+
+ mBatteryChecker = new BatteryChecker(
+ new AMessage(kWhatBatteryChecker, this), kBatteryTimeoutUs);
+
+ (new AMessage(ops[0].event, this))->post();
+
+ // wait for done
+ AutoMutex lock(mLock);
+ EXPECT_NE(TIMED_OUT, mDone.waitRelative(mLock, timeoutUs * 1000ll));
+ }
+
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+ friend class BatteryCheckerTest;
+};
+
+class BatteryCheckerTest : public ::testing::Test {
+public:
+ BatteryCheckerTest()
+ : mLooper(new ALooper)
+ , mHandler(new BatteryCheckerTestHandler()) {
+ mLooper->setName("BatterCheckerLooper");
+ mLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
+ mLooper->registerHandler(mHandler);
+ }
+
+protected:
+ using EventType = BatteryCheckerTestHandler::EventType;
+ using Operation = BatteryCheckerTestHandler::Operation;
+
+ virtual ~BatteryCheckerTest() {
+ mLooper->stop();
+ mLooper->unregisterHandler(mHandler->id());
+ }
+
+ void runTest(const std::vector<Operation> &ops, int64_t timeoutUs) {
+ mHandler->runTest(ops, timeoutUs);
+ }
+
+ sp<ALooper> mLooper;
+ sp<BatteryCheckerTestHandler> mHandler;
+};
+
+void BatteryCheckerTestHandler::onMessageReceived(const sp<AMessage> &msg) {
+ switch(msg->what()) {
+ case kWhatStart:
+ mBatteryChecker->setExecuting(true);
+ break;
+ case kWhatStop:
+ mBatteryChecker->setExecuting(false);
+ break;
+ case kWhatActivity:
+ mBatteryChecker->onCodecActivity([this] () { mOnCounter++; });
+ break;
+ case kWhatReleased:
+ mBatteryChecker->onClientRemoved();
+ break;
+ case kWhatBatteryChecker:
+ mBatteryChecker->onCheckBatteryTimer(msg, [this] () { mOffCounter++; });
+ break;
+ case kWhatCheckpoint:
+ // verify ON/OFF state and total events
+ EXPECT_EQ(mOnCounter, mOps[0].expectedOnCounter);
+ EXPECT_EQ(mOffCounter, mOps[0].expectedOffCounter);
+ break;
+ default:
+ TRESPASS();
+ }
+ if (msg->what() != kWhatBatteryChecker) {
+ EXPECT_EQ(msg->what(), mOps[0].event);
+ // post next message
+ if (!mOps[0].repeatCount) {
+ mOps.erase(mOps.begin());
+ } else {
+ mOps[0].repeatCount--;
+ }
+ int64_t duration = mOps[0].delay;
+ if (!mOps.empty()) {
+ (new AMessage(mOps[0].event, this))->post(duration);
+ } else {
+ AutoMutex lock(mLock);
+ mDone.signal();
+ }
+ }
+}
+
+TEST_F(BatteryCheckerTest, testNormalOperations) {
+ runTest({
+ {EventType::kWhatStart, 0ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 0, 0},
+ {EventType::kWhatActivity, 33333ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 1, 0}, // ON
+ {EventType::kWhatActivity, 33333ll, 2*kWaitStatusChangeUs/33333ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 1, 0},
+ {EventType::kWhatCheckpoint, kWaitStatusChangeUs, 0, 1, 1}, // OFF
+ }, 10000000ll);
+}
+
+TEST_F(BatteryCheckerTest, testPauseResume) {
+ runTest({
+ {EventType::kWhatStart, 0ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 0, 0},
+ {EventType::kWhatActivity, 33333ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 1, 0}, // ON
+ {EventType::kWhatCheckpoint, kWaitStatusChangeUs, 0, 1, 1}, // OFF
+ {EventType::kWhatActivity, 33333ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 2, 1}, // ON
+ {EventType::kWhatCheckpoint, kWaitStatusChangeUs, 0, 2, 2}, // OFF
+ }, 10000000ll);
+}
+
+TEST_F(BatteryCheckerTest, testClientRemovedAndRestart) {
+ runTest({
+ {EventType::kWhatStart, 0ll},
+ {EventType::kWhatActivity, 33333ll, kWaitStatusChangeUs/33333ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 1, 0},
+
+ // stop executing state itself shouldn't trigger any calls
+ {EventType::kWhatStop, 0ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 1, 0},
+
+ // release shouldn't trigger any calls either,
+ // client resource will be removed entirely
+ {EventType::kWhatReleased, 0ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 1, 0},
+ {EventType::kWhatCheckpoint, kWaitStatusChangeUs, 0, 1, 0},
+
+ // start pushing buffers again, On should be received without any Off
+ {EventType::kWhatStart, 0ll},
+ {EventType::kWhatActivity, 33333ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 2, 0},
+
+ // double check that only new checker msg triggers OFF,
+ // left-over checker msg from stale generate discarded
+ {EventType::kWhatCheckpoint, kWaitStatusChangeUs, 0, 2, 1},
+ }, 10000000ll);
+}
+
+TEST_F(BatteryCheckerTest, testActivityWhileNotExecuting) {
+ runTest({
+ // activity before start shouldn't trigger
+ {EventType::kWhatActivity, 0ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 0, 0},
+
+ {EventType::kWhatStart, 0ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 0, 0},
+
+ // activity after start before stop should trigger
+ {EventType::kWhatActivity, 33333ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 1, 0},
+
+ // stop executing state itself shouldn't trigger any calls
+ {EventType::kWhatStop, 0ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 1, 0},
+
+ // keep pushing another 3 seconds after stop, expected to OFF
+ {EventType::kWhatActivity, 33333ll, kWaitStatusChangeUs/33333ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 1, 1},
+ }, 10000000ll);
+}
+
+TEST_F(BatteryCheckerTest, testSparseActivity) {
+ runTest({
+ {EventType::kWhatStart, 0ll},
+ {EventType::kWhatCheckpoint, 0ll, 0, 0, 0},
+
+ // activity arrives sparsely with interval only slightly small than timeout
+ // should only trigger 1 ON
+ {EventType::kWhatActivity, kSparseFrameIntervalUs, 2},
+ {EventType::kWhatCheckpoint, 0ll, 0, 1, 0},
+ {EventType::kWhatCheckpoint, kSparseFrameIntervalUs, 0, 1, 0},
+ {EventType::kWhatCheckpoint, kTestMarginUs, 0, 1, 1}, // OFF
+ }, 10000000ll);
+}
+} // namespace android
diff --git a/media/libstagefright/xmlparser/api/current.txt b/media/libstagefright/xmlparser/api/current.txt
index 9d7c57d..16c8af8 100644
--- a/media/libstagefright/xmlparser/api/current.txt
+++ b/media/libstagefright/xmlparser/api/current.txt
@@ -138,7 +138,12 @@
public class Variant {
ctor public Variant();
+ method public java.util.List<media.codecs.Alias> getAlias_optional();
+ method public java.util.List<media.codecs.Quirk> getAttribute_optional();
+ method public java.util.List<media.codecs.Feature> getFeature_optional();
+ method public java.util.List<media.codecs.Limit> getLimit_optional();
method public String getName();
+ method public java.util.List<media.codecs.Quirk> getQuirk_optional();
method public void setName(String);
}
diff --git a/media/libstagefright/xmlparser/media_codecs.xsd b/media/libstagefright/xmlparser/media_codecs.xsd
index 63ec5d0..3b5681f 100644
--- a/media/libstagefright/xmlparser/media_codecs.xsd
+++ b/media/libstagefright/xmlparser/media_codecs.xsd
@@ -107,6 +107,13 @@
<xs:attribute name="value" type="xs:string"/>
</xs:complexType>
<xs:complexType name="Variant">
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="Quirk" type="Quirk"/>
+ <xs:element name="Attribute" type="Quirk"/>
+ <xs:element name="Alias" type="Alias"/>
+ <xs:element name="Limit" type="Limit"/>
+ <xs:element name="Feature" type="Feature"/>
+ </xs:choice>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
<xs:complexType name="Setting">
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 355d945..b6da11e 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1933,7 +1933,8 @@
&output.notificationFrameCount,
callingPid, clientUid, &output.flags,
input.clientInfo.clientTid,
- &lStatus, portId);
+ &lStatus, portId,
+ input.opPackageName);
LOG_ALWAYS_FATAL_IF((lStatus == NO_ERROR) && (recordTrack == 0));
// lStatus == BAD_TYPE means FAST flag was rejected: request a new input from
diff --git a/services/audioflinger/RecordTracks.h b/services/audioflinger/RecordTracks.h
index 08660dd..c8397cd 100644
--- a/services/audioflinger/RecordTracks.h
+++ b/services/audioflinger/RecordTracks.h
@@ -19,6 +19,39 @@
#error This header file should only be included from AudioFlinger.h
#endif
+// Checks and monitors OP_RECORD_AUDIO
+class OpRecordAudioMonitor : public RefBase {
+public:
+ ~OpRecordAudioMonitor() override;
+ bool hasOpRecordAudio() const;
+
+ static sp<OpRecordAudioMonitor> createIfNeeded(uid_t uid, const String16& opPackageName);
+
+private:
+ OpRecordAudioMonitor(uid_t uid, const String16& opPackageName);
+ void onFirstRef() override;
+
+ AppOpsManager mAppOpsManager;
+
+ class RecordAudioOpCallback : public BnAppOpsCallback {
+ public:
+ explicit RecordAudioOpCallback(const wp<OpRecordAudioMonitor>& monitor);
+ void opChanged(int32_t op, const String16& packageName) override;
+
+ private:
+ const wp<OpRecordAudioMonitor> mMonitor;
+ };
+
+ sp<RecordAudioOpCallback> mOpCallback;
+ // called by RecordAudioOpCallback when OP_RECORD_AUDIO is updated in AppOp callback
+ // and in onFirstRef()
+ void checkRecordAudio();
+
+ std::atomic_bool mHasOpRecordAudio;
+ const uid_t mUid;
+ const String16 mPackage;
+};
+
// record track
class RecordTrack : public TrackBase {
public:
@@ -36,6 +69,7 @@
uid_t uid,
audio_input_flags_t flags,
track_type type,
+ const String16& opPackageName,
audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE);
virtual ~RecordTrack();
virtual status_t initCheck() const;
@@ -68,7 +102,7 @@
{ return (mFlags & AUDIO_INPUT_FLAG_DIRECT) != 0; }
void setSilenced(bool silenced) { if (!isPatchTrack()) mSilenced = silenced; }
- bool isSilenced() const { return mSilenced; }
+ bool isSilenced() const;
status_t getActiveMicrophones(std::vector<media::MicrophoneInfo>* activeMicrophones);
@@ -111,6 +145,11 @@
audio_input_flags_t mFlags;
bool mSilenced;
+
+ // used to enforce OP_RECORD_AUDIO
+ uid_t mUid;
+ String16 mOpPackageName;
+ sp<OpRecordAudioMonitor> mOpRecordAudioMonitor;
};
// playback track, used by PatchPanel
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 48ba9d4..868739f 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -7302,7 +7302,7 @@
// Sanitize before releasing if the track has no access to the source data
// An idle UID receives silence from non virtual devices until active
if (activeTrack->isSilenced()) {
- memset(activeTrack->mSink.raw, 0, framesOut * mFrameSize);
+ memset(activeTrack->mSink.raw, 0, framesOut * activeTrack->frameSize());
}
activeTrack->releaseBuffer(&activeTrack->mSink);
}
@@ -7463,7 +7463,8 @@
audio_input_flags_t *flags,
pid_t tid,
status_t *status,
- audio_port_handle_t portId)
+ audio_port_handle_t portId,
+ const String16& opPackageName)
{
size_t frameCount = *pFrameCount;
size_t notificationFrameCount = *pNotificationFrameCount;
@@ -7597,7 +7598,7 @@
track = new RecordTrack(this, client, attr, sampleRate,
format, channelMask, frameCount,
nullptr /* buffer */, (size_t)0 /* bufferSize */, sessionId, creatorPid, uid,
- *flags, TrackBase::TYPE_DEFAULT, portId);
+ *flags, TrackBase::TYPE_DEFAULT, opPackageName, portId);
lStatus = track->initCheck();
if (lStatus != NO_ERROR) {
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index fc8aa13..87bebf3 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -1553,7 +1553,8 @@
audio_input_flags_t *flags,
pid_t tid,
status_t *status /*non-NULL*/,
- audio_port_handle_t portId);
+ audio_port_handle_t portId,
+ const String16& opPackageName);
status_t start(RecordTrack* recordTrack,
AudioSystem::sync_event_t event,
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 1497f1c..7cf34c1 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -225,6 +225,8 @@
uint32_t channelCount() const { return mChannelCount; }
+ size_t frameSize() const { return mFrameSize; }
+
audio_channel_mask_t channelMask() const { return mChannelMask; }
virtual uint32_t sampleRate() const { return mSampleRate; }
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index e694ace..07f0538 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -437,7 +437,7 @@
return mHasOpPlayAudio.load();
}
-// Note this method is never called (and never to be) for audio server / root track
+// Note this method is never called (and never to be) for audio server / patch record track
// - not called from constructor due to check on UID,
// - not called from PlayAudioOpCallback because the callback is not installed in this case
void AudioFlinger::PlaybackThread::OpPlayAudioMonitor::checkPlayAudioForUsage()
@@ -1880,6 +1880,105 @@
// ----------------------------------------------------------------------------
// Record
// ----------------------------------------------------------------------------
+
+
+// ----------------------------------------------------------------------------
+// AppOp for audio recording
+// -------------------------------
+
+#undef LOG_TAG
+#define LOG_TAG "AF::OpRecordAudioMonitor"
+
+// static
+sp<AudioFlinger::RecordThread::OpRecordAudioMonitor>
+AudioFlinger::RecordThread::OpRecordAudioMonitor::createIfNeeded(
+ uid_t uid, const String16& opPackageName)
+{
+ if (isServiceUid(uid)) {
+ ALOGV("not silencing record for service uid:%d pack:%s",
+ uid, String8(opPackageName).string());
+ return nullptr;
+ }
+
+ if (opPackageName.size() == 0) {
+ Vector<String16> packages;
+ // no package name, happens with SL ES clients
+ // query package manager to find one
+ PermissionController permissionController;
+ permissionController.getPackagesForUid(uid, packages);
+ if (packages.isEmpty()) {
+ return nullptr;
+ } else {
+ ALOGV("using pack:%s for uid:%d", String8(packages[0]).string(), uid);
+ return new OpRecordAudioMonitor(uid, packages[0]);
+ }
+ }
+
+ return new OpRecordAudioMonitor(uid, opPackageName);
+}
+
+AudioFlinger::RecordThread::OpRecordAudioMonitor::OpRecordAudioMonitor(
+ uid_t uid, const String16& opPackageName)
+ : mHasOpRecordAudio(true), mUid(uid), mPackage(opPackageName)
+{
+}
+
+AudioFlinger::RecordThread::OpRecordAudioMonitor::~OpRecordAudioMonitor()
+{
+ if (mOpCallback != 0) {
+ mAppOpsManager.stopWatchingMode(mOpCallback);
+ }
+ mOpCallback.clear();
+}
+
+void AudioFlinger::RecordThread::OpRecordAudioMonitor::onFirstRef()
+{
+ checkRecordAudio();
+ mOpCallback = new RecordAudioOpCallback(this);
+ ALOGV("start watching OP_RECORD_AUDIO for pack:%s", String8(mPackage).string());
+ mAppOpsManager.startWatchingMode(AppOpsManager::OP_RECORD_AUDIO, mPackage, mOpCallback);
+}
+
+bool AudioFlinger::RecordThread::OpRecordAudioMonitor::hasOpRecordAudio() const {
+ return mHasOpRecordAudio.load();
+}
+
+// Called by RecordAudioOpCallback when OP_RECORD_AUDIO is updated in AppOp callback
+// and in onFirstRef()
+// Note this method is never called (and never to be) for audio server / root track
+// due to the UID in createIfNeeded(). As a result for those record track, it's:
+// - not called from constructor,
+// - not called from RecordAudioOpCallback because the callback is not installed in this case
+void AudioFlinger::RecordThread::OpRecordAudioMonitor::checkRecordAudio()
+{
+ const int32_t mode = mAppOpsManager.checkOp(AppOpsManager::OP_RECORD_AUDIO,
+ mUid, mPackage);
+ const bool hasIt = (mode == AppOpsManager::MODE_ALLOWED);
+ // verbose logging only log when appOp changed
+ ALOGI_IF(hasIt != mHasOpRecordAudio.load(),
+ "OP_RECORD_AUDIO missing, %ssilencing record uid%d pack:%s",
+ hasIt ? "un" : "", mUid, String8(mPackage).string());
+ mHasOpRecordAudio.store(hasIt);
+}
+
+AudioFlinger::RecordThread::OpRecordAudioMonitor::RecordAudioOpCallback::RecordAudioOpCallback(
+ const wp<OpRecordAudioMonitor>& monitor) : mMonitor(monitor)
+{ }
+
+void AudioFlinger::RecordThread::OpRecordAudioMonitor::RecordAudioOpCallback::opChanged(int32_t op,
+ const String16& packageName) {
+ UNUSED(packageName);
+ if (op != AppOpsManager::OP_RECORD_AUDIO) {
+ return;
+ }
+ sp<OpRecordAudioMonitor> monitor = mMonitor.promote();
+ if (monitor != NULL) {
+ monitor->checkRecordAudio();
+ }
+}
+
+
+
#undef LOG_TAG
#define LOG_TAG "AF::RecordHandle"
@@ -1951,6 +2050,7 @@
uid_t uid,
audio_input_flags_t flags,
track_type type,
+ const String16& opPackageName,
audio_port_handle_t portId)
: TrackBase(thread, client, attr, sampleRate, format,
channelMask, frameCount, buffer, bufferSize, sessionId,
@@ -1964,7 +2064,8 @@
mResamplerBufferProvider(NULL), // initialize in case of early constructor exit
mRecordBufferConverter(NULL),
mFlags(flags),
- mSilenced(false)
+ mSilenced(false),
+ mOpRecordAudioMonitor(OpRecordAudioMonitor::createIfNeeded(uid, opPackageName))
{
if (mCblk == NULL) {
return;
@@ -2215,6 +2316,14 @@
mServerLatencyMs.store(latencyMs);
}
+bool AudioFlinger::RecordThread::RecordTrack::isSilenced() const {
+ if (mSilenced) {
+ return true;
+ }
+ // The monitor is only created for record tracks that can be silenced.
+ return mOpRecordAudioMonitor ? !mOpRecordAudioMonitor->hasOpRecordAudio() : false;
+}
+
status_t AudioFlinger::RecordThread::RecordTrack::getActiveMicrophones(
std::vector<media::MicrophoneInfo>* activeMicrophones)
{
@@ -2265,7 +2374,7 @@
audio_attributes_t{} /* currently unused for patch track */,
sampleRate, format, channelMask, frameCount,
buffer, bufferSize, AUDIO_SESSION_NONE, getpid(), AID_AUDIOSERVER,
- flags, TYPE_PATCH),
+ flags, TYPE_PATCH, String16()),
PatchTrackBase(new ClientProxy(mCblk, mBuffer, frameCount, mFrameSize, false, true),
*recordThread, timeout)
{
diff --git a/services/camera/libcameraservice/device3/Camera3Stream.cpp b/services/camera/libcameraservice/device3/Camera3Stream.cpp
index fd9b4b0..f707ef8 100644
--- a/services/camera/libcameraservice/device3/Camera3Stream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Stream.cpp
@@ -70,7 +70,7 @@
mFormatOverridden(false),
mOriginalFormat(format),
mDataSpaceOverridden(false),
- mOriginalDataSpace(HAL_DATASPACE_UNKNOWN),
+ mOriginalDataSpace(dataSpace),
mPhysicalCameraId(physicalCameraId),
mLastTimestamp(0) {
@@ -137,9 +137,6 @@
void Camera3Stream::setDataSpaceOverride(bool dataSpaceOverridden) {
mDataSpaceOverridden = dataSpaceOverridden;
- if (dataSpaceOverridden && mOriginalDataSpace == HAL_DATASPACE_UNKNOWN) {
- mOriginalDataSpace = camera3_stream::data_space;
- }
}
bool Camera3Stream::isDataSpaceOverridden() const {
diff --git a/services/camera/libcameraservice/device3/Camera3Stream.h b/services/camera/libcameraservice/device3/Camera3Stream.h
index 67afd0f..805df82 100644
--- a/services/camera/libcameraservice/device3/Camera3Stream.h
+++ b/services/camera/libcameraservice/device3/Camera3Stream.h
@@ -588,7 +588,7 @@
//Keep track of original dataSpace in case it gets overridden
bool mDataSpaceOverridden;
- android_dataspace mOriginalDataSpace;
+ const android_dataspace mOriginalDataSpace;
String8 mPhysicalCameraId;
nsecs_t mLastTimestamp;
diff --git a/services/mediaanalytics/iface_statsd.cpp b/services/mediaanalytics/iface_statsd.cpp
index 6845f06..6fd4415 100644
--- a/services/mediaanalytics/iface_statsd.cpp
+++ b/services/mediaanalytics/iface_statsd.cpp
@@ -60,6 +60,7 @@
{ "audiotrack", statsd_audiotrack },
{ "codec", statsd_codec},
{ "drm.vendor.Google.WidevineCDM", statsd_widevineCDM },
+ { "drmmanager", statsd_drmmanager },
{ "extractor", statsd_extractor },
{ "mediadrm", statsd_mediadrm },
{ "nuplayer", statsd_nuplayer },
diff --git a/services/mediaanalytics/iface_statsd.h b/services/mediaanalytics/iface_statsd.h
index f85d303..014929b 100644
--- a/services/mediaanalytics/iface_statsd.h
+++ b/services/mediaanalytics/iface_statsd.h
@@ -30,5 +30,6 @@
extern bool statsd_mediadrm(MediaAnalyticsItem *);
extern bool statsd_widevineCDM(MediaAnalyticsItem *);
+extern bool statsd_drmmanager(MediaAnalyticsItem *);
} // namespace android
diff --git a/services/mediaanalytics/statsd_drm.cpp b/services/mediaanalytics/statsd_drm.cpp
index 902483a..845383d 100644
--- a/services/mediaanalytics/statsd_drm.cpp
+++ b/services/mediaanalytics/statsd_drm.cpp
@@ -104,4 +104,38 @@
return true;
}
+// drmmanager
+bool statsd_drmmanager(MediaAnalyticsItem *item)
+{
+ if (item == NULL) return false;
+
+ nsecs_t timestamp = item->getTimestamp();
+ std::string pkgName = item->getPkgName();
+ int64_t pkgVersionCode = item->getPkgVersionCode();
+ int64_t mediaApexVersion = 0;
+
+ char *plugin_id = NULL;
+ (void) item->getCString("plugin_id", &plugin_id);
+ char *description = NULL;
+ (void) item->getCString("description", &description);
+ int32_t method_id = -1;
+ (void) item->getInt32("method_id", &method_id);
+ char *mime_types = NULL;
+ (void) item->getCString("mime_types", &mime_types);
+
+ if (enabled_statsd) {
+ android::util::stats_write(android::util::MEDIAMETRICS_DRMMANAGER_REPORTED,
+ timestamp, pkgName.c_str(), pkgVersionCode,
+ mediaApexVersion,
+ plugin_id, description,
+ method_id, mime_types);
+ } else {
+ ALOGV("NOT sending: drmmanager data");
+ }
+
+ free(plugin_id);
+ free(description);
+ free(mime_types);
+ return true;
+}
} // namespace android
diff --git a/services/mediaresourcemanager/ResourceManagerService.cpp b/services/mediaresourcemanager/ResourceManagerService.cpp
index 5a52b3d..bdcd5e4 100644
--- a/services/mediaresourcemanager/ResourceManagerService.cpp
+++ b/services/mediaresourcemanager/ResourceManagerService.cpp
@@ -200,16 +200,44 @@
return OK;
}
-ResourceManagerService::ResourceManagerService()
- : ResourceManagerService(new ProcessInfo()) {}
+struct SystemCallbackImpl :
+ public ResourceManagerService::SystemCallbackInterface {
+ SystemCallbackImpl() {}
-ResourceManagerService::ResourceManagerService(sp<ProcessInfoInterface> processInfo)
+ virtual void noteStartVideo(int uid) override {
+ BatteryNotifier::getInstance().noteStartVideo(uid);
+ }
+ virtual void noteStopVideo(int uid) override {
+ BatteryNotifier::getInstance().noteStopVideo(uid);
+ }
+ virtual void noteResetVideo() override {
+ BatteryNotifier::getInstance().noteResetVideo();
+ }
+ virtual bool requestCpusetBoost(
+ bool enable, const sp<IInterface> &client) override {
+ return android::requestCpusetBoost(enable, client);
+ }
+
+protected:
+ virtual ~SystemCallbackImpl() {}
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(SystemCallbackImpl);
+};
+
+ResourceManagerService::ResourceManagerService()
+ : ResourceManagerService(new ProcessInfo(), new SystemCallbackImpl()) {}
+
+ResourceManagerService::ResourceManagerService(
+ const sp<ProcessInfoInterface> &processInfo,
+ const sp<SystemCallbackInterface> &systemResource)
: mProcessInfo(processInfo),
+ mSystemCB(systemResource),
mServiceLog(new ServiceLog()),
mSupportsMultipleSecureCodecs(true),
mSupportsSecureWithNonSecureCodec(true),
mCpuBoostCount(0) {
- BatteryNotifier::getInstance().noteResetVideo();
+ mSystemCB->noteResetVideo();
}
ResourceManagerService::~ResourceManagerService() {}
@@ -238,13 +266,13 @@
// Request it on every new instance of kCpuBoost, as the media.codec
// could have died, if we only do it the first time subsequent instances
// never gets the boost.
- if (requestCpusetBoost(true, this) != OK) {
+ if (mSystemCB->requestCpusetBoost(true, this) != OK) {
ALOGW("couldn't request cpuset boost");
}
mCpuBoostCount++;
} else if (resource.mType == MediaResource::kBattery
&& resource.mSubType == MediaResource::kVideoCodec) {
- BatteryNotifier::getInstance().noteStartVideo(clientInfo.uid);
+ mSystemCB->noteStartVideo(clientInfo.uid);
}
}
@@ -254,11 +282,11 @@
&& resource.mSubType == MediaResource::kUnspecifiedSubType
&& mCpuBoostCount > 0) {
if (--mCpuBoostCount == 0) {
- requestCpusetBoost(false, this);
+ mSystemCB->requestCpusetBoost(false, this);
}
} else if (resource.mType == MediaResource::kBattery
&& resource.mSubType == MediaResource::kVideoCodec) {
- BatteryNotifier::getInstance().noteStopVideo(clientInfo.uid);
+ mSystemCB->noteStopVideo(clientInfo.uid);
}
}
diff --git a/services/mediaresourcemanager/ResourceManagerService.h b/services/mediaresourcemanager/ResourceManagerService.h
index b9147ff..f086dc3 100644
--- a/services/mediaresourcemanager/ResourceManagerService.h
+++ b/services/mediaresourcemanager/ResourceManagerService.h
@@ -51,12 +51,22 @@
public BnResourceManagerService
{
public:
+ struct SystemCallbackInterface : public RefBase {
+ virtual void noteStartVideo(int uid) = 0;
+ virtual void noteStopVideo(int uid) = 0;
+ virtual void noteResetVideo() = 0;
+ virtual bool requestCpusetBoost(
+ bool enable, const sp<IInterface> &client) = 0;
+ };
+
static char const *getServiceName() { return "media.resource_manager"; }
virtual status_t dump(int fd, const Vector<String16>& args);
ResourceManagerService();
- explicit ResourceManagerService(sp<ProcessInfoInterface> processInfo);
+ explicit ResourceManagerService(
+ const sp<ProcessInfoInterface> &processInfo,
+ const sp<SystemCallbackInterface> &systemResource);
// IResourceManagerService interface
virtual void config(const Vector<MediaResourcePolicy> &policies);
@@ -118,6 +128,7 @@
mutable Mutex mLock;
sp<ProcessInfoInterface> mProcessInfo;
+ sp<SystemCallbackInterface> mSystemCB;
sp<ServiceLog> mServiceLog;
PidResourceInfosMap mMap;
bool mSupportsMultipleSecureCodecs;
diff --git a/services/mediaresourcemanager/test/ResourceManagerService_test.cpp b/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
index be592f5..ae97ec8 100644
--- a/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
+++ b/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
@@ -52,6 +52,62 @@
DISALLOW_EVIL_CONSTRUCTORS(TestProcessInfo);
};
+struct TestSystemCallback :
+ public ResourceManagerService::SystemCallbackInterface {
+ TestSystemCallback() :
+ mLastEvent({EventType::INVALID, 0}), mEventCount(0) {}
+
+ enum EventType {
+ INVALID = -1,
+ VIDEO_ON = 0,
+ VIDEO_OFF = 1,
+ VIDEO_RESET = 2,
+ CPUSET_ENABLE = 3,
+ CPUSET_DISABLE = 4,
+ };
+
+ struct EventEntry {
+ EventType type;
+ int arg;
+ };
+
+ virtual void noteStartVideo(int uid) override {
+ mLastEvent = {EventType::VIDEO_ON, uid};
+ mEventCount++;
+ }
+
+ virtual void noteStopVideo(int uid) override {
+ mLastEvent = {EventType::VIDEO_OFF, uid};
+ mEventCount++;
+ }
+
+ virtual void noteResetVideo() override {
+ mLastEvent = {EventType::VIDEO_RESET, 0};
+ mEventCount++;
+ }
+
+ virtual bool requestCpusetBoost(
+ bool enable, const sp<IInterface> &/*client*/) override {
+ mLastEvent = {enable ? EventType::CPUSET_ENABLE : EventType::CPUSET_DISABLE, 0};
+ mEventCount++;
+ return true;
+ }
+
+ size_t eventCount() { return mEventCount; }
+ EventType lastEventType() { return mLastEvent.type; }
+ EventEntry lastEvent() { return mLastEvent; }
+
+protected:
+ virtual ~TestSystemCallback() {}
+
+private:
+ EventEntry mLastEvent;
+ size_t mEventCount;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TestSystemCallback);
+};
+
+
struct TestClient : public BnResourceManagerClient {
TestClient(int pid, sp<ResourceManagerService> service)
: mReclaimed(false), mPid(pid), mService(service) {}
@@ -95,10 +151,17 @@
static const int kMidPriorityPid = 25;
static const int kHighPriorityPid = 10;
+using EventType = TestSystemCallback::EventType;
+using EventEntry = TestSystemCallback::EventEntry;
+bool operator== (const EventEntry& lhs, const EventEntry& rhs) {
+ return lhs.type == rhs.type && lhs.arg == rhs.arg;
+}
+
class ResourceManagerServiceTest : public ::testing::Test {
public:
ResourceManagerServiceTest()
- : mService(new ResourceManagerService(new TestProcessInfo)),
+ : mSystemCB(new TestSystemCallback()),
+ mService(new ResourceManagerService(new TestProcessInfo, mSystemCB)),
mTestClient1(new TestClient(kTestPid1, mService)),
mTestClient2(new TestClient(kTestPid2, mService)),
mTestClient3(new TestClient(kTestPid2, mService)) {
@@ -578,6 +641,84 @@
EXPECT_TRUE(mService->isCallingPriorityHigher_l(99, 100));
}
+ void testBatteryStats() {
+ // reset should always be called when ResourceManagerService is created (restarted)
+ EXPECT_EQ(1u, mSystemCB->eventCount());
+ EXPECT_EQ(EventType::VIDEO_RESET, mSystemCB->lastEventType());
+
+ // new client request should cause VIDEO_ON
+ Vector<MediaResource> resources1;
+ resources1.push_back(MediaResource(MediaResource::kBattery, MediaResource::kVideoCodec, 1));
+ mService->addResource(kTestPid1, kTestUid1, getId(mTestClient1), mTestClient1, resources1);
+ EXPECT_EQ(2u, mSystemCB->eventCount());
+ EXPECT_EQ(EventEntry({EventType::VIDEO_ON, kTestUid1}), mSystemCB->lastEvent());
+
+ // each client should only cause 1 VIDEO_ON
+ mService->addResource(kTestPid1, kTestUid1, getId(mTestClient1), mTestClient1, resources1);
+ EXPECT_EQ(2u, mSystemCB->eventCount());
+
+ // new client request should cause VIDEO_ON
+ Vector<MediaResource> resources2;
+ resources2.push_back(MediaResource(MediaResource::kBattery, MediaResource::kVideoCodec, 2));
+ mService->addResource(kTestPid2, kTestUid2, getId(mTestClient2), mTestClient2, resources2);
+ EXPECT_EQ(3u, mSystemCB->eventCount());
+ EXPECT_EQ(EventEntry({EventType::VIDEO_ON, kTestUid2}), mSystemCB->lastEvent());
+
+ // partially remove mTestClient1's request, shouldn't be any VIDEO_OFF
+ mService->removeResource(kTestPid1, getId(mTestClient1), resources1);
+ EXPECT_EQ(3u, mSystemCB->eventCount());
+
+ // remove mTestClient1's request, should be VIDEO_OFF for kTestUid1
+ // (use resource2 to test removing more instances than previously requested)
+ mService->removeResource(kTestPid1, getId(mTestClient1), resources2);
+ EXPECT_EQ(4u, mSystemCB->eventCount());
+ EXPECT_EQ(EventEntry({EventType::VIDEO_OFF, kTestUid1}), mSystemCB->lastEvent());
+
+ // remove mTestClient2, should be VIDEO_OFF for kTestUid2
+ mService->removeClient(kTestPid2, getId(mTestClient2));
+ EXPECT_EQ(5u, mSystemCB->eventCount());
+ EXPECT_EQ(EventEntry({EventType::VIDEO_OFF, kTestUid2}), mSystemCB->lastEvent());
+ }
+
+ void testCpusetBoost() {
+ // reset should always be called when ResourceManagerService is created (restarted)
+ EXPECT_EQ(1u, mSystemCB->eventCount());
+ EXPECT_EQ(EventType::VIDEO_RESET, mSystemCB->lastEventType());
+
+ // new client request should cause CPUSET_ENABLE
+ Vector<MediaResource> resources1;
+ resources1.push_back(MediaResource(MediaResource::kCpuBoost, 1));
+ mService->addResource(kTestPid1, kTestUid1, getId(mTestClient1), mTestClient1, resources1);
+ EXPECT_EQ(2u, mSystemCB->eventCount());
+ EXPECT_EQ(EventType::CPUSET_ENABLE, mSystemCB->lastEventType());
+
+ // each client should only cause 1 CPUSET_ENABLE
+ mService->addResource(kTestPid1, kTestUid1, getId(mTestClient1), mTestClient1, resources1);
+ EXPECT_EQ(2u, mSystemCB->eventCount());
+
+ // new client request should cause CPUSET_ENABLE
+ Vector<MediaResource> resources2;
+ resources2.push_back(MediaResource(MediaResource::kCpuBoost, 2));
+ mService->addResource(kTestPid2, kTestUid2, getId(mTestClient2), mTestClient2, resources2);
+ EXPECT_EQ(3u, mSystemCB->eventCount());
+ EXPECT_EQ(EventType::CPUSET_ENABLE, mSystemCB->lastEventType());
+
+ // remove mTestClient2 should not cause CPUSET_DISABLE, mTestClient1 still active
+ mService->removeClient(kTestPid2, getId(mTestClient2));
+ EXPECT_EQ(3u, mSystemCB->eventCount());
+
+ // remove 1 cpuboost from mTestClient1, should not be CPUSET_DISABLE (still 1 left)
+ mService->removeResource(kTestPid1, getId(mTestClient1), resources1);
+ EXPECT_EQ(3u, mSystemCB->eventCount());
+
+ // remove 2 cpuboost from mTestClient1, should be CPUSET_DISABLE
+ // (use resource2 to test removing more than previously requested)
+ mService->removeResource(kTestPid1, getId(mTestClient1), resources2);
+ EXPECT_EQ(4u, mSystemCB->eventCount());
+ EXPECT_EQ(EventType::CPUSET_DISABLE, mSystemCB->lastEventType());
+ }
+
+ sp<TestSystemCallback> mSystemCB;
sp<ResourceManagerService> mService;
sp<IResourceManagerClient> mTestClient1;
sp<IResourceManagerClient> mTestClient2;
@@ -629,4 +770,12 @@
testIsCallingPriorityHigher();
}
+TEST_F(ResourceManagerServiceTest, testBatteryStats) {
+ testBatteryStats();
+}
+
+TEST_F(ResourceManagerServiceTest, testCpusetBoost) {
+ testCpusetBoost();
+}
+
} // namespace android