Merge "TranscodingService: Enable java generation for transcoding aidl"
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+ license_type: NOTICE
+}
diff --git a/camera/ICameraClient.cpp b/camera/ICameraClient.cpp
index 8620f36..487b8b0 100644
--- a/camera/ICameraClient.cpp
+++ b/camera/ICameraClient.cpp
@@ -143,6 +143,11 @@
if (data.dataAvail() > 0) {
metadata = new camera_frame_metadata_t;
metadata->number_of_faces = data.readInt32();
+ if (metadata->number_of_faces <= 0 ||
+ metadata->number_of_faces > (int32_t)(INT32_MAX / sizeof(camera_face_t))) {
+ ALOGE("%s: Too large face count: %d", __FUNCTION__, metadata->number_of_faces);
+ return BAD_VALUE;
+ }
metadata->faces = (camera_face_t *) data.readInplace(
sizeof(camera_face_t) * metadata->number_of_faces);
}
diff --git a/camera/cameraserver/cameraserver.rc b/camera/cameraserver/cameraserver.rc
index a9aae0b..8f51458 100644
--- a/camera/cameraserver/cameraserver.rc
+++ b/camera/cameraserver/cameraserver.rc
@@ -3,5 +3,5 @@
user cameraserver
group audio camera input drmrpc
ioprio rt 4
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
rlimit rtprio 10 10
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h
index 8aec80d..9cabd8b 100644
--- a/include/private/media/AudioTrackShared.h
+++ b/include/private/media/AudioTrackShared.h
@@ -614,6 +614,8 @@
// and thus which resulted in an underrun.
virtual uint32_t getUnderrunFrames() const { return mCblk->u.mStreaming.mUnderrunFrames; }
+ virtual uint32_t getUnderrunCount() const { return mCblk->u.mStreaming.mUnderrunCount; }
+
// Return the playback speed and pitch read atomically. Not multi-thread safe on server side.
AudioPlaybackRate getPlaybackRate();
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index e221b95..206f87f 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -17,6 +17,34 @@
"include-filter": "android.media.cts.DecodeEditEncodeTest"
}
]
+ },
+ {
+ "name": "GtsMediaTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
+ }
+ ]
+ },
+ {
+ "name": "GtsExoPlayerTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.SocPresubmit"
+ },
+ {
+ "include-filter": "com.google.android.exoplayer.gts.DashTest#testWidevine23FpsH264Fixed"
+ }
+ ]
+ }
+ ],
+
+ "imports": [
+ {
+ "path": "frameworks/av/drm/mediadrm/plugins"
}
],
diff --git a/media/audioserver/audioserver.rc b/media/audioserver/audioserver.rc
index 38c2750..6cb0c73 100644
--- a/media/audioserver/audioserver.rc
+++ b/media/audioserver/audioserver.rc
@@ -5,7 +5,7 @@
group audio camera drmrpc media mediadrm net_bt net_bt_admin net_bw_acct wakelock
capabilities BLOCK_SUSPEND
ioprio rt 4
- writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+ task_profiles ProcessCapacityHigh HighPerformance
onrestart setprop sys.audio.restart.hal 1
diff --git a/media/bufferpool/2.0/Accessor.cpp b/media/bufferpool/2.0/Accessor.cpp
index 57b4609..e05b12a 100644
--- a/media/bufferpool/2.0/Accessor.cpp
+++ b/media/bufferpool/2.0/Accessor.cpp
@@ -117,6 +117,10 @@
Accessor::Impl::createInvalidator();
}
+void Accessor::createEvictor() {
+ Accessor::Impl::createEvictor();
+}
+
// Methods from ::android::hardware::media::bufferpool::V2_0::IAccessor follow.
Return<void> Accessor::connect(
const sp<::android::hardware::media::bufferpool::V2_0::IObserver>& observer,
diff --git a/media/bufferpool/2.0/Accessor.h b/media/bufferpool/2.0/Accessor.h
index 8d02519..8b43301 100644
--- a/media/bufferpool/2.0/Accessor.h
+++ b/media/bufferpool/2.0/Accessor.h
@@ -187,6 +187,8 @@
static void createInvalidator();
+ static void createEvictor();
+
private:
class Impl;
std::shared_ptr<Impl> mImpl;
diff --git a/media/bufferpool/2.0/AccessorImpl.cpp b/media/bufferpool/2.0/AccessorImpl.cpp
index 1947656..51d9a69 100644
--- a/media/bufferpool/2.0/AccessorImpl.cpp
+++ b/media/bufferpool/2.0/AccessorImpl.cpp
@@ -39,6 +39,9 @@
static constexpr size_t kMinAllocBytesForEviction = 1024*1024*15;
static constexpr size_t kMinBufferCountForEviction = 25;
+
+ static constexpr nsecs_t kEvictGranularityNs = 1000000000; // 1 sec
+ static constexpr nsecs_t kEvictDurationNs = 5000000000; // 5 secs
}
// Buffer structure in bufferpool process
@@ -135,11 +138,18 @@
return false;
}
-uint32_t Accessor::Impl::sSeqId = time(nullptr);
+#ifdef __ANDROID_VNDK__
+static constexpr uint32_t kSeqIdVndkBit = 1 << 31;
+#else
+static constexpr uint32_t kSeqIdVndkBit = 0;
+#endif
+
+static constexpr uint32_t kSeqIdMax = 0x7fffffff;
+uint32_t Accessor::Impl::sSeqId = time(nullptr) & kSeqIdMax;
Accessor::Impl::Impl(
const std::shared_ptr<BufferPoolAllocator> &allocator)
- : mAllocator(allocator) {}
+ : mAllocator(allocator), mScheduleEvictTs(0) {}
Accessor::Impl::~Impl() {
}
@@ -157,7 +167,7 @@
std::lock_guard<std::mutex> lock(mBufferPool.mMutex);
if (newConnection) {
int32_t pid = getpid();
- ConnectionId id = (int64_t)pid << 32 | sSeqId;
+ ConnectionId id = (int64_t)pid << 32 | sSeqId | kSeqIdVndkBit;
status = mBufferPool.mObserver.open(id, statusDescPtr);
if (status == ResultStatus::OK) {
newConnection->initialize(accessor, id);
@@ -167,7 +177,7 @@
mBufferPool.mConnectionIds.insert(id);
mBufferPool.mInvalidationChannel.getDesc(invDescPtr);
mBufferPool.mInvalidation.onConnect(id, observer);
- if (sSeqId == UINT32_MAX) {
+ if (sSeqId == kSeqIdMax) {
sSeqId = 0;
} else {
++sSeqId;
@@ -177,6 +187,7 @@
}
mBufferPool.processStatusMessages();
mBufferPool.cleanUp();
+ scheduleEvictIfNeeded();
}
return status;
}
@@ -191,6 +202,7 @@
// Since close# will be called after all works are finished, it is OK to
// evict unused buffers.
mBufferPool.cleanUp(true);
+ scheduleEvictIfNeeded();
return ResultStatus::OK;
}
@@ -217,6 +229,7 @@
mBufferPool.handleOwnBuffer(connectionId, *bufferId);
}
mBufferPool.cleanUp();
+ scheduleEvictIfNeeded();
return status;
}
@@ -242,6 +255,7 @@
}
}
mBufferPool.cleanUp();
+ scheduleEvictIfNeeded();
return ResultStatus::CRITICAL_ERROR;
}
@@ -884,6 +898,88 @@
}
}
+void Accessor::Impl::evictorThread(
+ std::map<const std::weak_ptr<Accessor::Impl>, nsecs_t, std::owner_less<>> &accessors,
+ std::mutex &mutex,
+ std::condition_variable &cv) {
+ std::list<const std::weak_ptr<Accessor::Impl>> evictList;
+ while (true) {
+ int expired = 0;
+ int evicted = 0;
+ {
+ nsecs_t now = systemTime();
+ std::unique_lock<std::mutex> lock(mutex);
+ if (accessors.size() == 0) {
+ cv.wait(lock);
+ }
+ auto it = accessors.begin();
+ while (it != accessors.end()) {
+ if (now > (it->second + kEvictDurationNs)) {
+ ++expired;
+ evictList.push_back(it->first);
+ it = accessors.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+ // evict idle accessors;
+ for (auto it = evictList.begin(); it != evictList.end(); ++it) {
+ const std::shared_ptr<Accessor::Impl> accessor = it->lock();
+ if (accessor) {
+ accessor->cleanUp(true);
+ ++evicted;
+ }
+ }
+ if (expired > 0) {
+ ALOGD("evictor expired: %d, evicted: %d", expired, evicted);
+ }
+ evictList.clear();
+ ::usleep(kEvictGranularityNs / 1000);
+ }
+}
+
+Accessor::Impl::AccessorEvictor::AccessorEvictor() {
+ std::thread evictor(
+ evictorThread,
+ std::ref(mAccessors),
+ std::ref(mMutex),
+ std::ref(mCv));
+ evictor.detach();
+}
+
+void Accessor::Impl::AccessorEvictor::addAccessor(
+ const std::weak_ptr<Accessor::Impl> &impl, nsecs_t ts) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ bool notify = mAccessors.empty();
+ auto it = mAccessors.find(impl);
+ if (it == mAccessors.end()) {
+ mAccessors.emplace(impl, ts);
+ } else {
+ it->second = ts;
+ }
+ if (notify) {
+ mCv.notify_one();
+ }
+}
+
+std::unique_ptr<Accessor::Impl::AccessorEvictor> Accessor::Impl::sEvictor;
+
+void Accessor::Impl::createEvictor() {
+ if (!sEvictor) {
+ sEvictor = std::make_unique<Accessor::Impl::AccessorEvictor>();
+ }
+}
+
+void Accessor::Impl::scheduleEvictIfNeeded() {
+ nsecs_t now = systemTime();
+
+ if (now > (mScheduleEvictTs + kEvictGranularityNs)) {
+ mScheduleEvictTs = now;
+ sEvictor->addAccessor(shared_from_this(), now);
+ }
+}
+
} // namespace implementation
} // namespace V2_0
} // namespace bufferpool
diff --git a/media/bufferpool/2.0/AccessorImpl.h b/media/bufferpool/2.0/AccessorImpl.h
index 9888be5..cd1b4d0 100644
--- a/media/bufferpool/2.0/AccessorImpl.h
+++ b/media/bufferpool/2.0/AccessorImpl.h
@@ -20,6 +20,7 @@
#include <map>
#include <set>
#include <condition_variable>
+#include <utils/Timers.h>
#include "Accessor.h"
namespace android {
@@ -71,6 +72,8 @@
static void createInvalidator();
+ static void createEvictor();
+
private:
// ConnectionId = pid : (timestamp_created + seqId)
// in order to guarantee uniqueness for each connection
@@ -78,6 +81,8 @@
const std::shared_ptr<BufferPoolAllocator> mAllocator;
+ nsecs_t mScheduleEvictTs;
+
/**
* Buffer pool implementation.
*
@@ -389,6 +394,25 @@
std::mutex &mutex,
std::condition_variable &cv,
bool &ready);
+
+ struct AccessorEvictor {
+ std::map<const std::weak_ptr<Accessor::Impl>, nsecs_t, std::owner_less<>> mAccessors;
+ std::mutex mMutex;
+ std::condition_variable mCv;
+
+ AccessorEvictor();
+ void addAccessor(const std::weak_ptr<Accessor::Impl> &impl, nsecs_t ts);
+ };
+
+ static std::unique_ptr<AccessorEvictor> sEvictor;
+
+ static void evictorThread(
+ std::map<const std::weak_ptr<Accessor::Impl>, nsecs_t, std::owner_less<>> &accessors,
+ std::mutex &mutex,
+ std::condition_variable &cv);
+
+ void scheduleEvictIfNeeded();
+
};
} // namespace implementation
diff --git a/media/bufferpool/2.0/ClientManager.cpp b/media/bufferpool/2.0/ClientManager.cpp
index 87ee4e8..54a20b9 100644
--- a/media/bufferpool/2.0/ClientManager.cpp
+++ b/media/bufferpool/2.0/ClientManager.cpp
@@ -484,6 +484,7 @@
sInstance = new ClientManager();
}
Accessor::createInvalidator();
+ Accessor::createEvictor();
return sInstance;
}
diff --git a/media/codec2/hidl/1.0/vts/functional/Android.bp b/media/codec2/hidl/1.0/vts/functional/Android.bp
index 5f0ba7d..cd3be81 100644
--- a/media/codec2/hidl/1.0/vts/functional/Android.bp
+++ b/media/codec2/hidl/1.0/vts/functional/Android.bp
@@ -89,6 +89,8 @@
"res/bbb_av1_176_144.av1",
"res/bbb_av1_640_360.info",
"res/bbb_av1_176_144.info",
+ "res/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9",
+ "res/bbb_vp9_704x480_280kbps_24fps_altref_2.info",
],
}
diff --git a/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp b/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp
index 5d4b001..da8225c 100644
--- a/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp
@@ -115,7 +115,8 @@
typedef std::unique_lock<std::mutex> ULock;
ULock l(queueLock);
workQueue.push_back(std::move(work));
- if (!flushedIndices.empty()) {
+ if (!flushedIndices.empty() &&
+ (frameIndexIt != flushedIndices.end())) {
flushedIndices.erase(frameIndexIt);
}
queueCondition.notify_all();
diff --git a/media/codec2/hidl/1.0/vts/functional/component/VtsHalMediaC2V1_0TargetComponentTest.cpp b/media/codec2/hidl/1.0/vts/functional/component/VtsHalMediaC2V1_0TargetComponentTest.cpp
index 119ce14..6122225 100644
--- a/media/codec2/hidl/1.0/vts/functional/component/VtsHalMediaC2V1_0TargetComponentTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/component/VtsHalMediaC2V1_0TargetComponentTest.cpp
@@ -330,7 +330,7 @@
TEST_P(Codec2ComponentInputTests, InputBufferTest) {
description("Tests for different inputs");
- uint32_t flags = std::stol(std::get<2>(GetParam()));
+ uint32_t flags = std::stoul(std::get<2>(GetParam()));
bool isNullBuffer = !std::get<3>(GetParam()).compare("true");
if (isNullBuffer)
ALOGD("Testing for null input buffer with flag : %u", flags);
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.info b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.info
new file mode 100644
index 0000000..9ea1ffa
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.info
@@ -0,0 +1,352 @@
+73 0 0
+159 16 41000
+103 16 41000
+34 0 41000
+30 0 83000
+30 0 125000
+30 0 166000
+1 0 208000
+105 0 250000
+54 0 291000
+65 0 333000
+1 0 375000
+239 0 416000
+1373 16 458000
+263 16 458000
+133 0 458000
+109 0 500000
+127 0 541000
+1 0 583000
+134 0 625000
+159 0 666000
+131 0 708000
+1 0 750000
+4420 16 791000
+349 16 791000
+151 0 791000
+1 0 833000
+120 0 875000
+1 0 916000
+3203 16 958000
+433 16 958000
+125 0 958000
+1 0 1000000
+107 0 1041000
+1 0 1083000
+3832 16 1125000
+541 16 1125000
+116 0 1125000
+1 0 1166000
+115 0 1208000
+1 0 1250000
+3777 16 1291000
+495 16 1291000
+109 0 1291000
+1 0 1333000
+79 0 1375000
+1 0 1416000
+3906 16 1458000
+539 16 1458000
+149 0 1458000
+1 0 1500000
+112 0 1541000
+1 0 1583000
+5495 16 1625000
+594 16 1625000
+392 0 1625000
+235 0 1666000
+170 0 1708000
+1 0 1750000
+384 0 1791000
+378 0 1833000
+120 0 1875000
+1 0 1916000
+16843 16 1958000
+1951 16 1958000
+774 0 1958000
+831 0 2000000
+560 0 2041000
+489 0 2083000
+345 0 2125000
+243 0 2166000
+260 0 2208000
+1 0 2250000
+498 0 2291000
+463 0 2333000
+414 0 2375000
+341 0 2416000
+262 0 2458000
+194 0 2500000
+85 0 2541000
+1 0 2583000
+11253 16 2625000
+1561 16 2625000
+250 0 2625000
+402 0 2666000
+454 0 2708000
+350 0 2750000
+316 0 2791000
+164 0 2833000
+108 0 2875000
+1 0 2916000
+246 0 2958000
+586 0 3000000
+493 0 3041000
+363 0 3083000
+325 0 3125000
+170 0 3166000
+78 0 3208000
+40 0 3250000
+15630 16 3291000
+2228 16 3291000
+346 0 3291000
+707 0 3333000
+685 0 3375000
+582 0 3416000
+473 0 3458000
+249 0 3500000
+177 0 3541000
+1 0 3583000
+541 0 3625000
+834 0 3666000
+614 0 3708000
+473 0 3750000
+365 0 3791000
+211 0 3833000
+91 0 3875000
+1 0 3916000
+8384 16 3958000
+1138 16 3958000
+256 0 3958000
+377 0 4000000
+316 0 4041000
+267 0 4083000
+119 0 4125000
+1 0 4166000
+319 0 4208000
+390 0 4250000
+243 0 4291000
+203 0 4333000
+110 0 4375000
+1 0 4416000
+11302 16 4458000
+1527 16 4458000
+408 0 4458000
+507 0 4500000
+350 0 4541000
+377 0 4583000
+239 0 4625000
+125 0 4666000
+1 0 4708000
+351 0 4750000
+469 0 4791000
+288 0 4833000
+244 0 4875000
+224 0 4916000
+108 0 4958000
+1 0 5000000
+6016 16 5041000
+561 16 5041000
+235 0 5041000
+213 0 5083000
+118 0 5125000
+1 0 5166000
+299 0 5208000
+213 0 5250000
+129 0 5291000
+1 0 5333000
+7029 16 5375000
+728 16 5375000
+339 0 5375000
+287 0 5416000
+225 0 5458000
+147 0 5500000
+1 0 5541000
+270 0 5583000
+217 0 5625000
+138 0 5666000
+46 0 5708000
+32861 0 5750000
+16095 16 5791000
+2033 16 5791000
+318 0 5791000
+443 0 5833000
+361 0 5875000
+313 0 5916000
+274 0 5958000
+210 0 6000000
+134 0 6041000
+1 0 6083000
+295 0 6125000
+415 0 6166000
+330 0 6208000
+264 0 6250000
+242 0 6291000
+166 0 6333000
+116 0 6375000
+1 0 6416000
+9488 16 6458000
+1466 16 6458000
+378 0 6458000
+419 0 6500000
+335 0 6541000
+290 0 6583000
+315 0 6625000
+199 0 6666000
+131 0 6708000
+1 0 6750000
+342 0 6791000
+421 0 6833000
+306 0 6875000
+320 0 6916000
+245 0 6958000
+205 0 7000000
+143 0 7041000
+1 0 7083000
+16554 16 7125000
+1744 16 7125000
+482 0 7125000
+365 0 7166000
+352 0 7208000
+308 0 7250000
+295 0 7291000
+199 0 7333000
+149 0 7375000
+1 0 7416000
+360 0 7458000
+428 0 7500000
+377 0 7541000
+347 0 7583000
+270 0 7625000
+187 0 7666000
+130 0 7708000
+39 0 7750000
+14637 16 7791000
+1832 16 7791000
+422 0 7791000
+466 0 7833000
+375 0 7875000
+405 0 7916000
+352 0 7958000
+275 0 8000000
+173 0 8041000
+1 0 8083000
+484 0 8125000
+516 0 8166000
+497 0 8208000
+452 0 8250000
+428 0 8291000
+293 0 8333000
+190 0 8375000
+1 0 8416000
+11534 16 8458000
+1655 16 8458000
+446 0 8458000
+531 0 8500000
+465 0 8541000
+495 0 8583000
+402 0 8625000
+330 0 8666000
+227 0 8708000
+1 0 8750000
+568 0 8791000
+694 0 8833000
+382 0 8875000
+422 0 8916000
+280 0 8958000
+305 0 9000000
+203 0 9041000
+1 0 9083000
+17067 16 9125000
+1521 16 9125000
+428 0 9125000
+434 0 9166000
+359 0 9208000
+368 0 9250000
+240 0 9291000
+215 0 9333000
+211 0 9375000
+1 0 9416000
+346 0 9458000
+533 0 9500000
+391 0 9541000
+313 0 9583000
+326 0 9625000
+211 0 9666000
+233 0 9708000
+35 0 9750000
+23743 16 9791000
+1968 16 9791000
+277 0 9791000
+276 0 9833000
+285 0 9875000
+232 0 9916000
+179 0 9958000
+203 0 10000000
+121 0 10041000
+1 0 10083000
+318 0 10125000
+391 0 10166000
+362 0 10208000
+291 0 10250000
+235 0 10291000
+187 0 10333000
+117 0 10375000
+43 0 10416000
+12465 16 10458000
+1607 16 10458000
+280 0 10458000
+295 0 10500000
+244 0 10541000
+211 0 10583000
+171 0 10625000
+159 0 10666000
+106 0 10708000
+1 0 10750000
+216 0 10791000
+195 0 10833000
+180 0 10875000
+164 0 10916000
+156 0 10958000
+96 0 11000000
+1 0 11041000
+54547 0 11083000
+2920 16 11125000
+153 16 11125000
+117 0 11125000
+83 0 11166000
+86 0 11208000
+1 0 11250000
+92 0 11291000
+93 0 11333000
+80 0 11375000
+1 0 11416000
+1314 16 11458000
+311 16 11458000
+79 0 11458000
+65 0 11500000
+74 0 11541000
+61 0 11583000
+1 0 11625000
+65 0 11666000
+64 0 11708000
+49 0 11750000
+51 0 11791000
+1 0 11833000
+19309 0 11875000
+3848 16 11916000
+771 16 11916000
+167 0 11916000
+202 0 11958000
+190 0 12000000
+173 0 12041000
+128 0 12083000
+79 0 12125000
+1 0 12166000
+155 0 12208000
+211 0 12250000
+192 0 12291000
+131 0 12333000
+91 0 12375000
+128 0 12416000
+1 0 12458000
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9 b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9
new file mode 100644
index 0000000..00c7dec
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
index ec3fd7b..f216429 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
@@ -267,7 +267,7 @@
}
// number of elementary streams per component
-#define STREAM_COUNT 2
+#define STREAM_COUNT 3
// LookUpTable of clips and metadata for component testing
void GetURLForComponent(Codec2VideoDecHidlTest::standardComp comp, char* mURL, char* info,
size_t streamIndex = 1) {
@@ -280,29 +280,31 @@
static const CompToURL kCompToURL[] = {
{Codec2VideoDecHidlTest::standardComp::avc,
- {"bbb_avc_176x144_300kbps_60fps.h264", "bbb_avc_640x360_768kbps_30fps.h264"},
- {"bbb_avc_176x144_300kbps_60fps.info", "bbb_avc_640x360_768kbps_30fps.info"}},
+ {"bbb_avc_176x144_300kbps_60fps.h264", "bbb_avc_640x360_768kbps_30fps.h264", ""},
+ {"bbb_avc_176x144_300kbps_60fps.info", "bbb_avc_640x360_768kbps_30fps.info", ""}},
{Codec2VideoDecHidlTest::standardComp::hevc,
- {"bbb_hevc_176x144_176kbps_60fps.hevc", "bbb_hevc_640x360_1600kbps_30fps.hevc"},
- {"bbb_hevc_176x144_176kbps_60fps.info", "bbb_hevc_640x360_1600kbps_30fps.info"}},
+ {"bbb_hevc_176x144_176kbps_60fps.hevc", "bbb_hevc_640x360_1600kbps_30fps.hevc", ""},
+ {"bbb_hevc_176x144_176kbps_60fps.info", "bbb_hevc_640x360_1600kbps_30fps.info", ""}},
{Codec2VideoDecHidlTest::standardComp::mpeg2,
- {"bbb_mpeg2_176x144_105kbps_25fps.m2v", "bbb_mpeg2_352x288_1mbps_60fps.m2v"},
- {"bbb_mpeg2_176x144_105kbps_25fps.info", "bbb_mpeg2_352x288_1mbps_60fps.info"}},
+ {"bbb_mpeg2_176x144_105kbps_25fps.m2v", "bbb_mpeg2_352x288_1mbps_60fps.m2v", ""},
+ {"bbb_mpeg2_176x144_105kbps_25fps.info", "bbb_mpeg2_352x288_1mbps_60fps.info", ""}},
{Codec2VideoDecHidlTest::standardComp::h263,
- {"", "bbb_h263_352x288_300kbps_12fps.h263"},
- {"", "bbb_h263_352x288_300kbps_12fps.info"}},
+ {"", "bbb_h263_352x288_300kbps_12fps.h263", ""},
+ {"", "bbb_h263_352x288_300kbps_12fps.info", ""}},
{Codec2VideoDecHidlTest::standardComp::mpeg4,
- {"", "bbb_mpeg4_352x288_512kbps_30fps.m4v"},
- {"", "bbb_mpeg4_352x288_512kbps_30fps.info"}},
+ {"", "bbb_mpeg4_352x288_512kbps_30fps.m4v", ""},
+ {"", "bbb_mpeg4_352x288_512kbps_30fps.info", ""}},
{Codec2VideoDecHidlTest::standardComp::vp8,
- {"bbb_vp8_176x144_240kbps_60fps.vp8", "bbb_vp8_640x360_2mbps_30fps.vp8"},
- {"bbb_vp8_176x144_240kbps_60fps.info", "bbb_vp8_640x360_2mbps_30fps.info"}},
+ {"bbb_vp8_176x144_240kbps_60fps.vp8", "bbb_vp8_640x360_2mbps_30fps.vp8", ""},
+ {"bbb_vp8_176x144_240kbps_60fps.info", "bbb_vp8_640x360_2mbps_30fps.info", ""}},
{Codec2VideoDecHidlTest::standardComp::vp9,
- {"bbb_vp9_176x144_285kbps_60fps.vp9", "bbb_vp9_640x360_1600kbps_30fps.vp9"},
- {"bbb_vp9_176x144_285kbps_60fps.info", "bbb_vp9_640x360_1600kbps_30fps.info"}},
+ {"bbb_vp9_176x144_285kbps_60fps.vp9", "bbb_vp9_640x360_1600kbps_30fps.vp9",
+ "bbb_vp9_704x480_280kbps_24fps_altref_2.vp9"},
+ {"bbb_vp9_176x144_285kbps_60fps.info", "bbb_vp9_640x360_1600kbps_30fps.info",
+ "bbb_vp9_704x480_280kbps_24fps_altref_2.info"}},
{Codec2VideoDecHidlTest::standardComp::av1,
- {"bbb_av1_640_360.av1", "bbb_av1_176_144.av1"},
- {"bbb_av1_640_360.info", "bbb_av1_176_144.info"}},
+ {"bbb_av1_640_360.av1", "bbb_av1_176_144.av1", ""},
+ {"bbb_av1_640_360.info", "bbb_av1_176_144.info", ""}},
};
for (size_t i = 0; i < sizeof(kCompToURL) / sizeof(kCompToURL[0]); ++i) {
@@ -481,7 +483,9 @@
eleInfo >> flags;
eleInfo >> timestamp;
bool codecConfig = flags ? ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0;
- if (mTimestampDevTest && !codecConfig) mTimestampUslist.push_back(timestamp);
+ bool nonDisplayFrame = ((flags & FLAG_NON_DISPLAY_FRAME) != 0);
+ if (mTimestampDevTest && !codecConfig && !nonDisplayFrame)
+ mTimestampUslist.push_back(timestamp);
Info.push_back({bytesCount, flags, timestamp});
}
eleInfo.close();
@@ -545,6 +549,10 @@
strcpy(mURL, sResourceDir.c_str());
strcpy(info, sResourceDir.c_str());
GetURLForComponent(mCompName, mURL, info, i % STREAM_COUNT);
+ if (!(strcmp(mURL, sResourceDir.c_str())) || !(strcmp(info, sResourceDir.c_str()))) {
+ ALOGV("Stream not available, skipping this index");
+ continue;
+ }
eleInfo.open(info);
ASSERT_EQ(eleInfo.is_open(), true) << mURL << " - file not found";
@@ -560,10 +568,12 @@
Info.push_back({bytesCount, flags, timestamp});
bool codecConfig =
flags ? ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0;
+ bool nonDisplayFrame = ((flags & FLAG_NON_DISPLAY_FRAME) != 0);
{
ULock l(mQueueLock);
- if (mTimestampDevTest && !codecConfig) mTimestampUslist.push_back(timestamp);
+ if (mTimestampDevTest && !codecConfig && !nonDisplayFrame)
+ mTimestampUslist.push_back(timestamp);
}
if (timestampMax < timestamp) timestampMax = timestamp;
}
@@ -903,6 +913,10 @@
std::make_tuple(std::get<0>(params), std::get<1>(params), "1", "false"));
kDecodeTestParameters.push_back(
std::make_tuple(std::get<0>(params), std::get<1>(params), "1", "true"));
+ kDecodeTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "2", "false"));
+ kDecodeTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "2", "true"));
}
// Set the resource directory based on command line args.
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
index 8761797..63e7a69 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
@@ -49,6 +49,8 @@
<option name="push-file" key="bbb_av1_176_144.av1" value="/data/local/tmp/media/bbb_av1_176_144.av1" />
<option name="push-file" key="bbb_av1_640_360.info" value="/data/local/tmp/media/bbb_av1_640_360.info" />
<option name="push-file" key="bbb_av1_176_144.info" value="/data/local/tmp/media/bbb_av1_176_144.info" />
+ <option name="push-file" key="bbb_vp9_704x480_280kbps_24fps_altref_2.vp9" value="/data/local/tmp/media/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9" />
+ <option name="push-file" key="bbb_vp9_704x480_280kbps_24fps_altref_2.info" value="/data/local/tmp/media/bbb_vp9_704x480_280kbps_24fps_altref_2.info" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp
index be97383..ecc56f5 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp
@@ -41,7 +41,8 @@
: C2Buffer({block->share(C2Rect(block->width(), block->height()), ::C2Fence())}) {}
};
-static std::vector<std::tuple<std::string, std::string, std::string>> kEncodeTestParameters;
+static std::vector<std::tuple<std::string, std::string, std::string, std::string>>
+ kEncodeTestParameters;
static std::vector<std::tuple<std::string, std::string, std::string, std::string>>
kEncodeResolutionTestParameters;
@@ -102,6 +103,7 @@
mFramesReceived = 0;
mFailedWorkReceived = 0;
mTimestampUs = 0u;
+ mOutputSize = 0u;
mTimestampDevTest = false;
if (mCompName == unknown_comp) mDisableTest = true;
if (mDisableTest) std::cout << "[ WARN ] Test Disabled \n";
@@ -159,6 +161,16 @@
}
if (work->result != C2_OK) mFailedWorkReceived++;
+ if (!work->worklets.front()->output.buffers.empty()) {
+ mOutputSize += work->worklets.front()
+ ->output.buffers[0]
+ ->data()
+ .linearBlocks()
+ .front()
+ .map()
+ .get()
+ .capacity();
+ }
workDone(mComponent, work, mFlushedIndices, mQueueLock, mQueueCondition, mWorkQueue,
mEos, mCsd, mFramesReceived);
}
@@ -186,6 +198,7 @@
uint32_t mFramesReceived;
uint32_t mFailedWorkReceived;
uint64_t mTimestampUs;
+ uint64_t mOutputSize;
std::list<uint64_t> mTimestampUslist;
std::list<uint64_t> mFlushedIndices;
@@ -281,7 +294,6 @@
uint32_t maxRetry = 0;
int bytesCount = nWidth * nHeight * 3 >> 1;
int32_t timestampIncr = ENCODER_TIMESTAMP_INCREMENT;
- uint64_t timestamp = 0;
c2_status_t err = C2_OK;
while (1) {
if (nFrames == 0) break;
@@ -308,7 +320,7 @@
}
work->input.flags = (C2FrameData::flags_t)flags;
- work->input.ordinal.timestamp = timestamp;
+ work->input.ordinal.timestamp = frameID * timestampIncr;
work->input.ordinal.frameIndex = frameID;
{
ULock l(queueLock);
@@ -361,7 +373,6 @@
ASSERT_EQ(component->queue(&items), C2_OK);
ALOGV("Frame #%d size = %d queued", frameID, bytesCount);
nFrames--;
- timestamp += timestampIncr;
frameID++;
maxRetry = 0;
}
@@ -376,7 +387,8 @@
class Codec2VideoEncEncodeTest
: public Codec2VideoEncHidlTestBase,
- public ::testing::WithParamInterface<std::tuple<std::string, std::string, std::string>> {
+ public ::testing::WithParamInterface<
+ std::tuple<std::string, std::string, std::string, std::string>> {
void getParams() {
mInstanceName = std::get<0>(GetParam());
mComponentName = std::get<1>(GetParam());
@@ -391,6 +403,8 @@
int32_t nWidth = ENC_DEFAULT_FRAME_WIDTH;
int32_t nHeight = ENC_DEFAULT_FRAME_HEIGHT;
bool signalEOS = !std::get<2>(GetParam()).compare("true");
+ // Send an empty frame to receive CSD data from encoder.
+ bool sendEmptyFirstFrame = !std::get<3>(GetParam()).compare("true");
strcpy(mURL, sResourceDir.c_str());
GetURLForComponent(mURL);
@@ -404,24 +418,32 @@
mTimestampDevTest = true;
mFlushedIndices.clear();
mTimestampUslist.clear();
- uint32_t inputFrames = ENC_NUM_FRAMES;
+ int32_t inputFrames = ENC_NUM_FRAMES + (sendEmptyFirstFrame ? 1 : 0);
uint32_t timestamp = 0;
+
// Add input timestamp to timestampUslist
while (inputFrames) {
if (mTimestampDevTest) mTimestampUslist.push_back(timestamp);
timestamp += ENCODER_TIMESTAMP_INCREMENT;
inputFrames--;
}
+
if (!setupConfigParam(nWidth, nHeight)) {
std::cout << "[ WARN ] Test Skipped \n";
return;
}
ASSERT_EQ(mComponent->start(), C2_OK);
+
+ if (sendEmptyFirstFrame) {
+ ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue, 0, false));
+ inputFrames += 1;
+ }
ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
- mFlushedIndices, mGraphicPool, eleStream, mDisableTest, 0,
- ENC_NUM_FRAMES, nWidth, nHeight, false, signalEOS));
+ mFlushedIndices, mGraphicPool, eleStream, mDisableTest,
+ inputFrames, ENC_NUM_FRAMES, nWidth, nHeight, false,
+ signalEOS));
// mDisableTest will be set if buffer was not fetched properly.
- // This may happen when resolution is not proper but config suceeded
+ // This may happen when resolution is not proper but config succeeded
// In this cases, we skip encoding the input stream
if (mDisableTest) {
std::cout << "[ WARN ] Test Disabled \n";
@@ -430,7 +452,7 @@
}
// If EOS is not sent, sending empty input with EOS flag
- inputFrames = ENC_NUM_FRAMES;
+ inputFrames += ENC_NUM_FRAMES;
if (!signalEOS) {
ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, 1));
ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
@@ -531,7 +553,7 @@
mFlushedIndices, mGraphicPool, eleStream, mDisableTest, 0,
numFramesFlushed, nWidth, nHeight));
// mDisableTest will be set if buffer was not fetched properly.
- // This may happen when resolution is not proper but config suceeded
+ // This may happen when resolution is not proper but config succeeded
// In this cases, we skip encoding the input stream
if (mDisableTest) {
std::cout << "[ WARN ] Test Disabled \n";
@@ -568,7 +590,7 @@
nHeight, true));
eleStream.close();
// mDisableTest will be set if buffer was not fetched properly.
- // This may happen when resolution is not proper but config suceeded
+ // This may happen when resolution is not proper but config succeeded
// In this cases, we skip encoding the input stream
if (mDisableTest) {
std::cout << "[ WARN ] Test Disabled \n";
@@ -677,7 +699,7 @@
MAX_INPUT_BUFFERS, nWidth, nHeight, false, true));
// mDisableTest will be set if buffer was not fetched properly.
- // This may happen when resolution is not proper but config suceeded
+ // This may happen when resolution is not proper but config succeeded
// In this cases, we skip encoding the input stream
if (mDisableTest) {
std::cout << "[ WARN ] Test Disabled \n";
@@ -703,15 +725,105 @@
INSTANTIATE_TEST_SUITE_P(EncodeTestwithEOS, Codec2VideoEncEncodeTest,
::testing::ValuesIn(kEncodeTestParameters));
+TEST_P(Codec2VideoEncHidlTest, AdaptiveBitrateTest) {
+ description("Encodes input file for different bitrates");
+ if (mDisableTest) GTEST_SKIP() << "Test is disabled";
+
+ char mURL[512];
+
+ strcpy(mURL, sResourceDir.c_str());
+ GetURLForComponent(mURL);
+
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true) << mURL << " file not found";
+ ALOGV("mURL : %s", mURL);
+
+ mFlushedIndices.clear();
+
+ int32_t nWidth = ENC_DEFAULT_FRAME_WIDTH;
+ int32_t nHeight = ENC_DEFAULT_FRAME_HEIGHT;
+ if (!setupConfigParam(nWidth, nHeight)) {
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+ ASSERT_EQ(mComponent->start(), C2_OK);
+
+ uint64_t prevOutputSize = 0u;
+ uint32_t bitrateValues[] = {100000, 64000, 200000};
+ uint32_t prevBitrate = 0;
+ int32_t inputFrameId = 0;
+
+ for (uint32_t curBitrate : bitrateValues) {
+ // Configuring bitrate
+ std::vector<std::unique_ptr<C2SettingResult>> failures;
+ C2StreamBitrateInfo::output bitrate(0u, curBitrate);
+ std::vector<C2Param*> configParam{&bitrate};
+ c2_status_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures);
+ if (status != C2_OK && failures.size() != 0u) {
+ ALOGW("BitRate Config failed, using previous bitrate");
+ }
+
+ ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mGraphicPool, eleStream,
+ mDisableTest, inputFrameId, ENC_NUM_FRAMES, nWidth,
+ nHeight, false, false));
+ // mDisableTest will be set if buffer was not fetched properly.
+ // This may happen when resolution is not proper but config succeeded
+ // In this cases, we skip encoding the input stream
+ if (mDisableTest) {
+ std::cout << "[ WARN ] Test Disabled \n";
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+ return;
+ }
+ inputFrameId += ENC_NUM_FRAMES;
+ // blocking call to ensures application to Wait till all the inputs are
+ // consumed
+ ALOGD("Waiting for input consumption");
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+ // Change in bitrate may result in different outputSize
+ if (prevBitrate >= curBitrate) {
+ EXPECT_LE(mOutputSize, prevOutputSize);
+ } else {
+ EXPECT_GE(mOutputSize, prevOutputSize);
+ }
+ prevBitrate = curBitrate;
+ prevOutputSize = mOutputSize;
+ // Reset the file pointer and output size
+ mOutputSize = 0;
+ eleStream.seekg(0, eleStream.beg);
+ }
+
+ // Sending empty input with EOS flag
+ ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
+ C2FrameData::FLAG_END_OF_STREAM, false));
+ inputFrameId += 1;
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+ eleStream.close();
+ if (mFramesReceived != inputFrameId) {
+ ALOGE("Input buffer count and Output buffer count mismatch");
+ ALOGE("framesReceived : %d inputFrames : %d", mFramesReceived, inputFrameId);
+ ASSERT_TRUE(false);
+ }
+
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+}
+
} // anonymous namespace
int main(int argc, char** argv) {
kTestParameters = getTestParameters(C2Component::DOMAIN_VIDEO, C2Component::KIND_ENCODER);
for (auto params : kTestParameters) {
kEncodeTestParameters.push_back(
- std::make_tuple(std::get<0>(params), std::get<1>(params), "true"));
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "true", "true"));
kEncodeTestParameters.push_back(
- std::make_tuple(std::get<0>(params), std::get<1>(params), "false"));
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "true", "false"));
+ kEncodeTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "false", "true"));
+ kEncodeTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "false", "false"));
kEncodeResolutionTestParameters.push_back(
std::make_tuple(std::get<0>(params), std::get<1>(params), "52", "18"));
@@ -721,6 +833,10 @@
std::make_tuple(std::get<0>(params), std::get<1>(params), "484", "362"));
kEncodeResolutionTestParameters.push_back(
std::make_tuple(std::get<0>(params), std::get<1>(params), "244", "488"));
+ kEncodeResolutionTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "852", "608"));
+ kEncodeResolutionTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "1400", "442"));
}
// Set the resource directory based on command line args.
@@ -734,4 +850,4 @@
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
-}
\ No newline at end of file
+}
diff --git a/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h b/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h
index d3a693b..9c1a5cb 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h
+++ b/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h
@@ -22,6 +22,7 @@
#define ENC_DEFAULT_FRAME_WIDTH 352
#define ENC_DEFAULT_FRAME_HEIGHT 288
#define MAX_ITERATIONS 128
+#define FLAG_NON_DISPLAY_FRAME (1 << 4)
#define ALIGN(_sz, _align) ((_sz + (_align - 1)) & ~(_align - 1))
diff --git a/media/codec2/sfplugin/CCodec.cpp b/media/codec2/sfplugin/CCodec.cpp
index ab762d9..d4c723c 100644
--- a/media/codec2/sfplugin/CCodec.cpp
+++ b/media/codec2/sfplugin/CCodec.cpp
@@ -1406,6 +1406,9 @@
// TODO: convert err into status_t
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
}
+ // Assure buffers are not owned when stop() was called without flush().
+ std::list<std::unique_ptr<C2Work>> flushedWork;
+ mChannel->flush(flushedWork);
{
Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index e2be991..3773528 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -44,6 +44,7 @@
#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/SkipCutBuffer.h>
#include <media/MediaCodecBuffer.h>
+#include <mediadrm/ICrypto.h>
#include <system/window.h>
#include "CCodecBufferChannel.h"
@@ -127,6 +128,97 @@
count->value = -1;
}
+// CCodecBufferChannel::ReorderStash
+
+CCodecBufferChannel::ReorderStash::ReorderStash() {
+ clear();
+}
+
+void CCodecBufferChannel::ReorderStash::clear() {
+ mPending.clear();
+ mStash.clear();
+ mDepth = 0;
+ mKey = C2Config::ORDINAL;
+}
+
+void CCodecBufferChannel::ReorderStash::flush() {
+ mPending.clear();
+ mStash.clear();
+}
+
+void CCodecBufferChannel::ReorderStash::setDepth(uint32_t depth) {
+ mPending.splice(mPending.end(), mStash);
+ mDepth = depth;
+}
+
+void CCodecBufferChannel::ReorderStash::setKey(C2Config::ordinal_key_t key) {
+ mPending.splice(mPending.end(), mStash);
+ mKey = key;
+}
+
+bool CCodecBufferChannel::ReorderStash::pop(Entry *entry) {
+ if (mPending.empty()) {
+ return false;
+ }
+ entry->buffer = mPending.front().buffer;
+ entry->timestamp = mPending.front().timestamp;
+ entry->flags = mPending.front().flags;
+ entry->ordinal = mPending.front().ordinal;
+ mPending.pop_front();
+ return true;
+}
+
+void CCodecBufferChannel::ReorderStash::emplace(
+ const std::shared_ptr<C2Buffer> &buffer,
+ int64_t timestamp,
+ int32_t flags,
+ const C2WorkOrdinalStruct &ordinal) {
+ bool eos = flags & MediaCodec::BUFFER_FLAG_EOS;
+ if (!buffer && eos) {
+ // TRICKY: we may be violating ordering of the stash here. Because we
+ // don't expect any more emplace() calls after this, the ordering should
+ // not matter.
+ mStash.emplace_back(buffer, timestamp, flags, ordinal);
+ } else {
+ flags = flags & ~MediaCodec::BUFFER_FLAG_EOS;
+ auto it = mStash.begin();
+ for (; it != mStash.end(); ++it) {
+ if (less(ordinal, it->ordinal)) {
+ break;
+ }
+ }
+ mStash.emplace(it, buffer, timestamp, flags, ordinal);
+ if (eos) {
+ mStash.back().flags = mStash.back().flags | MediaCodec::BUFFER_FLAG_EOS;
+ }
+ }
+ while (!mStash.empty() && mStash.size() > mDepth) {
+ mPending.push_back(mStash.front());
+ mStash.pop_front();
+ }
+}
+
+void CCodecBufferChannel::ReorderStash::defer(
+ const CCodecBufferChannel::ReorderStash::Entry &entry) {
+ mPending.push_front(entry);
+}
+
+bool CCodecBufferChannel::ReorderStash::hasPending() const {
+ return !mPending.empty();
+}
+
+bool CCodecBufferChannel::ReorderStash::less(
+ const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2) {
+ switch (mKey) {
+ case C2Config::ORDINAL: return o1.frameIndex < o2.frameIndex;
+ case C2Config::TIMESTAMP: return o1.timestamp < o2.timestamp;
+ case C2Config::CUSTOM: return o1.customOrdinal < o2.customOrdinal;
+ default:
+ ALOGD("Unrecognized key; default to timestamp");
+ return o1.frameIndex < o2.frameIndex;
+ }
+}
+
// Input
CCodecBufferChannel::Input::Input() : extraBuffers("extra") {}
@@ -616,7 +708,7 @@
void CCodecBufferChannel::feedInputBufferIfAvailableInternal() {
if (mInputMetEos ||
- mOutput.lock()->buffers->hasPending() ||
+ mReorderStash.lock()->hasPending() ||
mPipelineWatcher.lock()->pipelineFull()) {
return;
} else {
@@ -889,6 +981,17 @@
return UNKNOWN_ERROR;
}
+ {
+ Mutexed<ReorderStash>::Locked reorder(mReorderStash);
+ reorder->clear();
+ if (reorderDepth) {
+ reorder->setDepth(reorderDepth.value);
+ }
+ if (reorderKey) {
+ reorder->setKey(reorderKey.value);
+ }
+ }
+
uint32_t inputDelayValue = inputDelay ? inputDelay.value : 0;
uint32_t pipelineDelayValue = pipelineDelay ? pipelineDelay.value : 0;
uint32_t outputDelayValue = outputDelay ? outputDelay.value : 0;
@@ -982,7 +1085,7 @@
// TODO: handle this without going into array mode
forceArrayMode = true;
} else {
- input->buffers.reset(new GraphicInputBuffers(numInputSlots, mName));
+ input->buffers.reset(new GraphicInputBuffers(mName));
}
} else {
if (hasCryptoOrDescrambler()) {
@@ -1150,20 +1253,13 @@
if (outputSurface || !buffersBoundToCodec) {
output->buffers.reset(new GraphicOutputBuffers(mName));
} else {
- output->buffers.reset(new RawGraphicOutputBuffers(numOutputSlots, mName));
+ output->buffers.reset(new RawGraphicOutputBuffers(mName));
}
} else {
output->buffers.reset(new LinearOutputBuffers(mName));
}
output->buffers->setFormat(outputFormat);
- output->buffers->clearStash();
- if (reorderDepth) {
- output->buffers->setReorderDepth(reorderDepth.value);
- }
- if (reorderKey) {
- output->buffers->setReorderKey(reorderKey.value);
- }
// Try to set output surface to created block pool if given.
if (outputSurface) {
@@ -1331,8 +1427,8 @@
{
Mutexed<Output>::Locked output(mOutput);
output->buffers->flush(flushedWork);
- output->buffers->flushStash();
}
+ mReorderStash.lock()->flush();
mPipelineWatcher.lock()->flush();
}
@@ -1368,34 +1464,45 @@
std::unique_ptr<C2Work> work,
const sp<AMessage> &outputFormat,
const C2StreamInitDataInfo::output *initData) {
- // Whether the output buffer should be reported to the client or not.
- bool notifyClient = false;
+ if (outputFormat != nullptr) {
+ Mutexed<Output>::Locked output(mOutput);
+ ALOGD("[%s] onWorkDone: output format changed to %s",
+ mName, outputFormat->debugString().c_str());
+ output->buffers->setFormat(outputFormat);
- if (work->result == C2_OK){
- notifyClient = true;
- } else if (work->result == C2_NOT_FOUND) {
- ALOGD("[%s] flushed work; ignored.", mName);
- } else {
- // C2_OK and C2_NOT_FOUND are the only results that we accept for processing
- // the config update.
- ALOGD("[%s] work failed to complete: %d", mName, work->result);
- mCCodecCallback->onError(work->result, ACTION_CODE_FATAL);
- return false;
+ AString mediaType;
+ if (outputFormat->findString(KEY_MIME, &mediaType)
+ && mediaType == MIMETYPE_AUDIO_RAW) {
+ int32_t channelCount;
+ int32_t sampleRate;
+ if (outputFormat->findInt32(KEY_CHANNEL_COUNT, &channelCount)
+ && outputFormat->findInt32(KEY_SAMPLE_RATE, &sampleRate)) {
+ output->buffers->updateSkipCutBuffer(sampleRate, channelCount);
+ }
+ }
}
- if ((work->input.ordinal.frameIndex -
- mFirstValidFrameIndex.load()).peek() < 0) {
+ if ((work->input.ordinal.frameIndex - mFirstValidFrameIndex.load()).peek() < 0) {
// Discard frames from previous generation.
ALOGD("[%s] Discard frames from previous generation.", mName);
- notifyClient = false;
+ return false;
}
if (mInputSurface == nullptr && (work->worklets.size() != 1u
|| !work->worklets.front()
- || !(work->worklets.front()->output.flags &
- C2FrameData::FLAG_INCOMPLETE))) {
- mPipelineWatcher.lock()->onWorkDone(
- work->input.ordinal.frameIndex.peeku());
+ || !(work->worklets.front()->output.flags & C2FrameData::FLAG_INCOMPLETE))) {
+ mPipelineWatcher.lock()->onWorkDone(work->input.ordinal.frameIndex.peeku());
+ }
+
+ if (work->result == C2_NOT_FOUND) {
+ ALOGD("[%s] flushed work; ignored.", mName);
+ return true;
+ }
+
+ if (work->result != C2_OK) {
+ ALOGD("[%s] work failed to complete: %d", mName, work->result);
+ mCCodecCallback->onError(work->result, ACTION_CODE_FATAL);
+ return false;
}
// NOTE: MediaCodec usage supposedly have only one worklet
@@ -1431,10 +1538,8 @@
case C2PortReorderBufferDepthTuning::CORE_INDEX: {
C2PortReorderBufferDepthTuning::output reorderDepth;
if (reorderDepth.updateFrom(*param)) {
- bool secure = mComponent->getName().find(".secure") !=
- std::string::npos;
- mOutput.lock()->buffers->setReorderDepth(
- reorderDepth.value);
+ bool secure = mComponent->getName().find(".secure") != std::string::npos;
+ mReorderStash.lock()->setDepth(reorderDepth.value);
ALOGV("[%s] onWorkDone: updated reorder depth to %u",
mName, reorderDepth.value);
size_t numOutputSlots = mOutput.lock()->numSlots;
@@ -1446,19 +1551,17 @@
output->maxDequeueBuffers += numInputSlots;
}
if (output->surface) {
- output->surface->setMaxDequeuedBufferCount(
- output->maxDequeueBuffers);
+ output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);
}
} else {
- ALOGD("[%s] onWorkDone: failed to read reorder depth",
- mName);
+ ALOGD("[%s] onWorkDone: failed to read reorder depth", mName);
}
break;
}
case C2PortReorderKeySetting::CORE_INDEX: {
C2PortReorderKeySetting::output reorderKey;
if (reorderKey.updateFrom(*param)) {
- mOutput.lock()->buffers->setReorderKey(reorderKey.value);
+ mReorderStash.lock()->setKey(reorderKey.value);
ALOGV("[%s] onWorkDone: updated reorder key to %u",
mName, reorderKey.value);
} else {
@@ -1473,8 +1576,7 @@
ALOGV("[%s] onWorkDone: updating pipeline delay %u",
mName, pipelineDelay.value);
newPipelineDelay = pipelineDelay.value;
- (void)mPipelineWatcher.lock()->pipelineDelay(
- pipelineDelay.value);
+ (void)mPipelineWatcher.lock()->pipelineDelay(pipelineDelay.value);
}
}
if (param->forInput()) {
@@ -1483,8 +1585,7 @@
ALOGV("[%s] onWorkDone: updating input delay %u",
mName, inputDelay.value);
newInputDelay = inputDelay.value;
- (void)mPipelineWatcher.lock()->inputDelay(
- inputDelay.value);
+ (void)mPipelineWatcher.lock()->inputDelay(inputDelay.value);
}
}
if (param->forOutput()) {
@@ -1492,10 +1593,8 @@
if (outputDelay.updateFrom(*param)) {
ALOGV("[%s] onWorkDone: updating output delay %u",
mName, outputDelay.value);
- bool secure = mComponent->getName().find(".secure") !=
- std::string::npos;
- (void)mPipelineWatcher.lock()->outputDelay(
- outputDelay.value);
+ bool secure = mComponent->getName().find(".secure") != std::string::npos;
+ (void)mPipelineWatcher.lock()->outputDelay(outputDelay.value);
bool outputBuffersChanged = false;
size_t numOutputSlots = 0;
@@ -1503,8 +1602,7 @@
{
Mutexed<Output>::Locked output(mOutput);
output->outputDelay = outputDelay.value;
- numOutputSlots = outputDelay.value +
- kSmoothnessFactor;
+ numOutputSlots = outputDelay.value + kSmoothnessFactor;
if (output->numSlots < numOutputSlots) {
output->numSlots = numOutputSlots;
if (output->buffers->isArrayMode()) {
@@ -1523,7 +1621,7 @@
mCCodecCallback->onOutputBuffersChanged();
}
- uint32_t depth = mOutput.lock()->buffers->getReorderDepth();
+ uint32_t depth = mReorderStash.lock()->depth();
Mutexed<OutputSurface>::Locked output(mOutputSurface);
output->maxDequeueBuffers = numOutputSlots + depth + kRenderingDepth;
if (!secure) {
@@ -1567,6 +1665,9 @@
ALOGV("[%s] onWorkDone: output EOS", mName);
}
+ sp<MediaCodecBuffer> outBuffer;
+ size_t index;
+
// WORKAROUND: adjust output timestamp based on client input timestamp and codec
// input timestamp. Codec output timestamp (in the timestamp field) shall correspond to
// the codec input timestamp, but client output timestamp should (reported in timeUs)
@@ -1587,18 +1688,8 @@
worklet->output.ordinal.timestamp.peekll(),
timestamp.peekll());
- // csd cannot be re-ordered and will always arrive first.
if (initData != nullptr) {
Mutexed<Output>::Locked output(mOutput);
- if (outputFormat) {
- output->buffers->updateSkipCutBuffer(outputFormat);
- output->buffers->setFormat(outputFormat);
- }
- if (!notifyClient) {
- return false;
- }
- size_t index;
- sp<MediaCodecBuffer> outBuffer;
if (output->buffers->registerCsd(initData, &index, &outBuffer) == OK) {
outBuffer->meta()->setInt64("timeUs", timestamp.peek());
outBuffer->meta()->setInt32("flags", MediaCodec::BUFFER_FLAG_CODECCONFIG);
@@ -1614,10 +1705,10 @@
}
}
- if (notifyClient && !buffer && !flags) {
- ALOGV("[%s] onWorkDone: Not reporting output buffer (%lld)",
+ if (!buffer && !flags && outputFormat == nullptr) {
+ ALOGV("[%s] onWorkDone: nothing to report from the work (%lld)",
mName, work->input.ordinal.frameIndex.peekull());
- notifyClient = false;
+ return true;
}
if (buffer) {
@@ -1636,60 +1727,63 @@
}
{
- Mutexed<Output>::Locked output(mOutput);
- output->buffers->pushToStash(
- buffer,
- notifyClient,
- timestamp.peek(),
- flags,
- outputFormat,
- worklet->output.ordinal);
+ Mutexed<ReorderStash>::Locked reorder(mReorderStash);
+ reorder->emplace(buffer, timestamp.peek(), flags, worklet->output.ordinal);
+ if (flags & MediaCodec::BUFFER_FLAG_EOS) {
+ // Flush reorder stash
+ reorder->setDepth(0);
+ }
}
sendOutputBuffers();
return true;
}
void CCodecBufferChannel::sendOutputBuffers() {
- OutputBuffers::BufferAction action;
- size_t index;
+ ReorderStash::Entry entry;
sp<MediaCodecBuffer> outBuffer;
- std::shared_ptr<C2Buffer> c2Buffer;
+ size_t index;
while (true) {
+ Mutexed<ReorderStash>::Locked reorder(mReorderStash);
+ if (!reorder->hasPending()) {
+ break;
+ }
+ if (!reorder->pop(&entry)) {
+ break;
+ }
+
Mutexed<Output>::Locked output(mOutput);
- action = output->buffers->popFromStashAndRegister(
- &c2Buffer, &index, &outBuffer);
- switch (action) {
- case OutputBuffers::SKIP:
- return;
- case OutputBuffers::DISCARD:
- break;
- case OutputBuffers::NOTIFY_CLIENT:
- output.unlock();
- mCallback->onOutputBufferAvailable(index, outBuffer);
- break;
- case OutputBuffers::REALLOCATE: {
+ status_t err = output->buffers->registerBuffer(entry.buffer, &index, &outBuffer);
+ if (err != OK) {
+ bool outputBuffersChanged = false;
+ if (err != WOULD_BLOCK) {
if (!output->buffers->isArrayMode()) {
- output->buffers =
- output->buffers->toArrayMode(output->numSlots);
+ output->buffers = output->buffers->toArrayMode(output->numSlots);
}
- static_cast<OutputBuffersArray*>(output->buffers.get())->
- realloc(c2Buffer);
- output.unlock();
+ OutputBuffersArray *array = (OutputBuffersArray *)output->buffers.get();
+ array->realloc(entry.buffer);
+ outputBuffersChanged = true;
+ }
+ ALOGV("[%s] sendOutputBuffers: unable to register output buffer", mName);
+ reorder->defer(entry);
+
+ output.unlock();
+ reorder.unlock();
+
+ if (outputBuffersChanged) {
mCCodecCallback->onOutputBuffersChanged();
}
return;
- case OutputBuffers::RETRY:
- ALOGV("[%s] sendOutputBuffers: unable to register output buffer",
- mName);
- return;
- default:
- LOG_ALWAYS_FATAL("[%s] sendOutputBuffers: "
- "corrupted BufferAction value (%d) "
- "returned from popFromStashAndRegister.",
- mName, int(action));
- return;
}
+ output.unlock();
+ reorder.unlock();
+
+ outBuffer->meta()->setInt64("timeUs", entry.timestamp);
+ outBuffer->meta()->setInt32("flags", entry.flags);
+ ALOGV("[%s] sendOutputBuffers: out buffer index = %zu [%p] => %p + %zu (%lld)",
+ mName, index, outBuffer.get(), outBuffer->data(), outBuffer->size(),
+ (long long)entry.timestamp);
+ mCallback->onOutputBufferAvailable(index, outBuffer);
}
}
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.h b/media/codec2/sfplugin/CCodecBufferChannel.h
index da15724..0263211 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.h
+++ b/media/codec2/sfplugin/CCodecBufferChannel.h
@@ -296,6 +296,48 @@
Mutexed<PipelineWatcher> mPipelineWatcher;
+ class ReorderStash {
+ public:
+ struct Entry {
+ inline Entry() : buffer(nullptr), timestamp(0), flags(0), ordinal({0, 0, 0}) {}
+ inline Entry(
+ const std::shared_ptr<C2Buffer> &b,
+ int64_t t,
+ int32_t f,
+ const C2WorkOrdinalStruct &o)
+ : buffer(b), timestamp(t), flags(f), ordinal(o) {}
+ std::shared_ptr<C2Buffer> buffer;
+ int64_t timestamp;
+ int32_t flags;
+ C2WorkOrdinalStruct ordinal;
+ };
+
+ ReorderStash();
+
+ void clear();
+ void flush();
+ void setDepth(uint32_t depth);
+ void setKey(C2Config::ordinal_key_t key);
+ bool pop(Entry *entry);
+ void emplace(
+ const std::shared_ptr<C2Buffer> &buffer,
+ int64_t timestamp,
+ int32_t flags,
+ const C2WorkOrdinalStruct &ordinal);
+ void defer(const Entry &entry);
+ bool hasPending() const;
+ uint32_t depth() const { return mDepth; }
+
+ private:
+ std::list<Entry> mPending;
+ std::list<Entry> mStash;
+ uint32_t mDepth;
+ C2Config::ordinal_key_t mKey;
+
+ bool less(const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2);
+ };
+ Mutexed<ReorderStash> mReorderStash;
+
std::atomic_bool mInputMetEos;
std::once_flag mRenderWarningFlag;
diff --git a/media/codec2/sfplugin/CCodecBuffers.cpp b/media/codec2/sfplugin/CCodecBuffers.cpp
index 4ce13aa..a9120c4 100644
--- a/media/codec2/sfplugin/CCodecBuffers.cpp
+++ b/media/codec2/sfplugin/CCodecBuffers.cpp
@@ -21,9 +21,9 @@
#include <C2PlatformSupport.h>
#include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/SkipCutBuffer.h>
+#include <mediadrm/ICrypto.h>
#include "CCodecBuffers.h"
@@ -122,6 +122,11 @@
// OutputBuffers
+OutputBuffers::OutputBuffers(const char *componentName, const char *name)
+ : CCodecBuffers(componentName, name) { }
+
+OutputBuffers::~OutputBuffers() = default;
+
void OutputBuffers::initSkipCutBuffer(
int32_t delay, int32_t padding, int32_t sampleRate, int32_t channelCount) {
CHECK(mSkipCutBuffer == nullptr);
@@ -150,29 +155,16 @@
setSkipCutBuffer(delay, padding);
}
-void OutputBuffers::updateSkipCutBuffer(
- const sp<AMessage> &format, bool notify) {
- AString mediaType;
- if (format->findString(KEY_MIME, &mediaType)
- && mediaType == MIMETYPE_AUDIO_RAW) {
- int32_t channelCount;
- int32_t sampleRate;
- if (format->findInt32(KEY_CHANNEL_COUNT, &channelCount)
- && format->findInt32(KEY_SAMPLE_RATE, &sampleRate)) {
- updateSkipCutBuffer(sampleRate, channelCount);
- }
- }
- if (notify) {
- mUnreportedFormat = nullptr;
- }
-}
-
void OutputBuffers::submit(const sp<MediaCodecBuffer> &buffer) {
if (mSkipCutBuffer != nullptr) {
mSkipCutBuffer->submit(buffer);
}
}
+void OutputBuffers::transferSkipCutBuffer(const sp<SkipCutBuffer> &scb) {
+ mSkipCutBuffer = scb;
+}
+
void OutputBuffers::setSkipCutBuffer(int32_t skip, int32_t cut) {
if (mSkipCutBuffer != nullptr) {
size_t prevSize = mSkipCutBuffer->size();
@@ -183,179 +175,13 @@
mSkipCutBuffer = new SkipCutBuffer(skip, cut, mChannelCount);
}
-void OutputBuffers::clearStash() {
- mPending.clear();
- mReorderStash.clear();
- mDepth = 0;
- mKey = C2Config::ORDINAL;
- mUnreportedFormat = nullptr;
-}
-
-void OutputBuffers::flushStash() {
- for (StashEntry& e : mPending) {
- e.notify = false;
- }
- for (StashEntry& e : mReorderStash) {
- e.notify = false;
- }
-}
-
-uint32_t OutputBuffers::getReorderDepth() const {
- return mDepth;
-}
-
-void OutputBuffers::setReorderDepth(uint32_t depth) {
- mPending.splice(mPending.end(), mReorderStash);
- mDepth = depth;
-}
-
-void OutputBuffers::setReorderKey(C2Config::ordinal_key_t key) {
- mPending.splice(mPending.end(), mReorderStash);
- mKey = key;
-}
-
-void OutputBuffers::pushToStash(
- const std::shared_ptr<C2Buffer>& buffer,
- bool notify,
- int64_t timestamp,
- int32_t flags,
- const sp<AMessage>& format,
- const C2WorkOrdinalStruct& ordinal) {
- bool eos = flags & MediaCodec::BUFFER_FLAG_EOS;
- if (!buffer && eos) {
- // TRICKY: we may be violating ordering of the stash here. Because we
- // don't expect any more emplace() calls after this, the ordering should
- // not matter.
- mReorderStash.emplace_back(
- buffer, notify, timestamp, flags, format, ordinal);
- } else {
- flags = flags & ~MediaCodec::BUFFER_FLAG_EOS;
- auto it = mReorderStash.begin();
- for (; it != mReorderStash.end(); ++it) {
- if (less(ordinal, it->ordinal)) {
- break;
- }
- }
- mReorderStash.emplace(it,
- buffer, notify, timestamp, flags, format, ordinal);
- if (eos) {
- mReorderStash.back().flags =
- mReorderStash.back().flags | MediaCodec::BUFFER_FLAG_EOS;
- }
- }
- while (!mReorderStash.empty() && mReorderStash.size() > mDepth) {
- mPending.push_back(mReorderStash.front());
- mReorderStash.pop_front();
- }
- ALOGV("[%s] %s: pushToStash -- pending size = %zu", mName, __func__, mPending.size());
-}
-
-OutputBuffers::BufferAction OutputBuffers::popFromStashAndRegister(
- std::shared_ptr<C2Buffer>* c2Buffer,
- size_t* index,
- sp<MediaCodecBuffer>* outBuffer) {
- if (mPending.empty()) {
- return SKIP;
- }
-
- // Retrieve the first entry.
- StashEntry &entry = mPending.front();
-
- *c2Buffer = entry.buffer;
- sp<AMessage> outputFormat = entry.format;
-
- // The output format can be processed without a registered slot.
- if (outputFormat) {
- ALOGD("[%s] popFromStashAndRegister: output format changed to %s",
- mName, outputFormat->debugString().c_str());
- updateSkipCutBuffer(outputFormat, entry.notify);
- }
-
- if (entry.notify) {
- if (outputFormat) {
- setFormat(outputFormat);
- } else if (mUnreportedFormat) {
- outputFormat = mUnreportedFormat->dup();
- setFormat(outputFormat);
- }
- mUnreportedFormat = nullptr;
- } else {
- if (outputFormat) {
- mUnreportedFormat = outputFormat;
- } else if (!mUnreportedFormat) {
- mUnreportedFormat = mFormat;
- }
- }
-
- // Flushing mReorderStash because no other buffers should come after output
- // EOS.
- if (entry.flags & MediaCodec::BUFFER_FLAG_EOS) {
- // Flush reorder stash
- setReorderDepth(0);
- }
-
- if (!entry.notify) {
- mPending.pop_front();
- return DISCARD;
- }
-
- // Try to register the buffer.
- status_t err = registerBuffer(*c2Buffer, index, outBuffer);
- if (err != OK) {
- if (err != WOULD_BLOCK) {
- return REALLOCATE;
- }
- return RETRY;
- }
-
- // Append information from the front stash entry to outBuffer.
- (*outBuffer)->meta()->setInt64("timeUs", entry.timestamp);
- (*outBuffer)->meta()->setInt32("flags", entry.flags);
- ALOGV("[%s] popFromStashAndRegister: "
- "out buffer index = %zu [%p] => %p + %zu (%lld)",
- mName, *index, outBuffer->get(),
- (*outBuffer)->data(), (*outBuffer)->size(),
- (long long)entry.timestamp);
-
- // The front entry of mPending will be removed now that the registration
- // succeeded.
- mPending.pop_front();
- return NOTIFY_CLIENT;
-}
-
-bool OutputBuffers::popPending(StashEntry *entry) {
- if (mPending.empty()) {
- return false;
- }
- *entry = mPending.front();
- mPending.pop_front();
- return true;
-}
-
-void OutputBuffers::deferPending(const OutputBuffers::StashEntry &entry) {
- mPending.push_front(entry);
-}
-
-bool OutputBuffers::hasPending() const {
- return !mPending.empty();
-}
-
-bool OutputBuffers::less(
- const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2) const {
- switch (mKey) {
- case C2Config::ORDINAL: return o1.frameIndex < o2.frameIndex;
- case C2Config::TIMESTAMP: return o1.timestamp < o2.timestamp;
- case C2Config::CUSTOM: return o1.customOrdinal < o2.customOrdinal;
- default:
- ALOGD("Unrecognized key; default to timestamp");
- return o1.frameIndex < o2.frameIndex;
- }
-}
-
// LocalBufferPool
-std::shared_ptr<LocalBufferPool> LocalBufferPool::Create(size_t poolCapacity) {
- return std::shared_ptr<LocalBufferPool>(new LocalBufferPool(poolCapacity));
+constexpr size_t kInitialPoolCapacity = kMaxLinearBufferSize;
+constexpr size_t kMaxPoolCapacity = kMaxLinearBufferSize * 32;
+
+std::shared_ptr<LocalBufferPool> LocalBufferPool::Create() {
+ return std::shared_ptr<LocalBufferPool>(new LocalBufferPool(kInitialPoolCapacity));
}
sp<ABuffer> LocalBufferPool::newBuffer(size_t capacity) {
@@ -375,6 +201,11 @@
mUsedSize -= mPool.back().capacity();
mPool.pop_back();
}
+ while (mUsedSize + capacity > mPoolCapacity && mPoolCapacity * 2 <= kMaxPoolCapacity) {
+ ALOGD("Increasing local buffer pool capacity from %zu to %zu",
+ mPoolCapacity, mPoolCapacity * 2);
+ mPoolCapacity *= 2;
+ }
if (mUsedSize + capacity > mPoolCapacity) {
ALOGD("mUsedSize = %zu, capacity = %zu, mPoolCapacity = %zu",
mUsedSize, capacity, mPoolCapacity);
@@ -960,11 +791,10 @@
// GraphicInputBuffers
GraphicInputBuffers::GraphicInputBuffers(
- size_t numInputSlots, const char *componentName, const char *name)
+ const char *componentName, const char *name)
: InputBuffers(componentName, name),
mImpl(mName),
- mLocalBufferPool(LocalBufferPool::Create(
- kMaxLinearBufferSize * numInputSlots)) { }
+ mLocalBufferPool(LocalBufferPool::Create()) { }
bool GraphicInputBuffers::requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) {
sp<Codec2Buffer> newBuffer = createNewBuffer();
@@ -1125,7 +955,7 @@
case C2BufferData::GRAPHIC: {
// This is only called for RawGraphicOutputBuffers.
mAlloc = [format = mFormat,
- lbp = LocalBufferPool::Create(kMaxLinearBufferSize * mImpl.arraySize())] {
+ lbp = LocalBufferPool::Create()] {
return ConstGraphicBlockBuffer::AllocateEmpty(
format,
[lbp](size_t capacity) {
@@ -1151,16 +981,6 @@
mImpl.grow(newSize, mAlloc);
}
-void OutputBuffersArray::transferFrom(OutputBuffers* source) {
- mFormat = source->mFormat;
- mSkipCutBuffer = source->mSkipCutBuffer;
- mUnreportedFormat = source->mUnreportedFormat;
- mPending = std::move(source->mPending);
- mReorderStash = std::move(source->mReorderStash);
- mDepth = source->mDepth;
- mKey = source->mKey;
-}
-
// FlexOutputBuffers
status_t FlexOutputBuffers::registerBuffer(
@@ -1203,12 +1023,13 @@
// track of the flushed work.
}
-std::unique_ptr<OutputBuffersArray> FlexOutputBuffers::toArrayMode(size_t size) {
+std::unique_ptr<OutputBuffers> FlexOutputBuffers::toArrayMode(size_t size) {
std::unique_ptr<OutputBuffersArray> array(new OutputBuffersArray(mComponentName.c_str()));
- array->transferFrom(this);
+ array->setFormat(mFormat);
+ array->transferSkipCutBuffer(mSkipCutBuffer);
std::function<sp<Codec2Buffer>()> alloc = getAlloc();
array->initialize(mImpl, size, alloc);
- return array;
+ return std::move(array);
}
size_t FlexOutputBuffers::numClientBuffers() const {
@@ -1271,10 +1092,9 @@
// RawGraphicOutputBuffers
RawGraphicOutputBuffers::RawGraphicOutputBuffers(
- size_t numOutputSlots, const char *componentName, const char *name)
+ const char *componentName, const char *name)
: FlexOutputBuffers(componentName, name),
- mLocalBufferPool(LocalBufferPool::Create(
- kMaxLinearBufferSize * numOutputSlots)) { }
+ mLocalBufferPool(LocalBufferPool::Create()) { }
sp<Codec2Buffer> RawGraphicOutputBuffers::wrap(const std::shared_ptr<C2Buffer> &buffer) {
if (buffer == nullptr) {
diff --git a/media/codec2/sfplugin/CCodecBuffers.h b/media/codec2/sfplugin/CCodecBuffers.h
index cadc4d8..6244acd 100644
--- a/media/codec2/sfplugin/CCodecBuffers.h
+++ b/media/codec2/sfplugin/CCodecBuffers.h
@@ -28,6 +28,8 @@
namespace android {
+struct ICrypto;
+class MemoryDealer;
class SkipCutBuffer;
constexpr size_t kLinearBufferSize = 1048576;
@@ -154,22 +156,15 @@
DISALLOW_EVIL_CONSTRUCTORS(InputBuffers);
};
-class OutputBuffersArray;
-
class OutputBuffers : public CCodecBuffers {
public:
- OutputBuffers(const char *componentName, const char *name = "Output")
- : CCodecBuffers(componentName, name) { }
- virtual ~OutputBuffers() = default;
+ OutputBuffers(const char *componentName, const char *name = "Output");
+ virtual ~OutputBuffers();
/**
* Register output C2Buffer from the component and obtain corresponding
- * index and MediaCodecBuffer object.
- *
- * Returns:
- * OK if registration succeeds.
- * NO_MEMORY if all buffers are available but not compatible.
- * WOULD_BLOCK if there are compatible buffers, but they are all in use.
+ * index and MediaCodecBuffer object. Returns false if registration
+ * fails.
*/
virtual status_t registerBuffer(
const std::shared_ptr<C2Buffer> &buffer,
@@ -204,7 +199,7 @@
* shall retain the internal state so that it will honor index and
* buffer from previous calls of registerBuffer().
*/
- virtual std::unique_ptr<OutputBuffersArray> toArrayMode(size_t size) = 0;
+ virtual std::unique_ptr<OutputBuffers> toArrayMode(size_t size) = 0;
/**
* Initialize SkipCutBuffer object.
@@ -213,164 +208,6 @@
int32_t delay, int32_t padding, int32_t sampleRate, int32_t channelCount);
/**
- * Update SkipCutBuffer from format. The @p format must not be null.
- * @p notify determines whether the format comes with a buffer that should
- * be reported to the client or not.
- */
- void updateSkipCutBuffer(const sp<AMessage> &format, bool notify = true);
-
- /**
- * Output Stash
- * ============
- *
- * The output stash is a place to hold output buffers temporarily before
- * they are registered to output slots. It has 2 main functions:
- * 1. Allow reordering of output frames as the codec may produce frames in a
- * different order.
- * 2. Act as a "buffer" between the codec and the client because the codec
- * may produce more buffers than available slots. This excess of codec's
- * output buffers should be registered to slots later, after the client
- * has released some slots.
- *
- * The stash consists of 2 lists of buffers: mPending and mReorderStash.
- * mPending is a normal FIFO queue with not size limit, while mReorderStash
- * is a sorted list with size limit mDepth.
- *
- * The normal flow of a non-csd output buffer is as follows:
- *
- * |----------------OutputBuffers---------------|
- * |----------Output stash----------| |
- * Codec --|-> mReorderStash --> mPending --|-> slots --|-> client
- * | | |
- * pushToStash() popFromStashAndRegister()
- *
- * The buffer that comes from the codec first enters mReorderStash. The
- * first buffer in mReorderStash gets moved to mPending when mReorderStash
- * overflows. Buffers in mPending are registered to slots and given to the
- * client as soon as slots are available.
- *
- * Every output buffer that is not a csd buffer should be put on the stash
- * by calling pushToStash(), then later registered to a slot by calling
- * popFromStashAndRegister() before notifying the client with
- * onOutputBufferAvailable().
- *
- * Reordering
- * ==========
- *
- * mReorderStash is a sorted list with a specified size limit. The size
- * limit can be set by calling setReorderDepth().
- *
- * Every buffer in mReorderStash has a C2WorkOrdinalStruct, which contains 3
- * members, all of which are comparable. Which member of C2WorkOrdinalStruct
- * should be used for reordering can be chosen by calling setReorderKey().
- */
-
- /**
- * Return the reorder depth---the size of mReorderStash.
- */
- uint32_t getReorderDepth() const;
-
- /**
- * Set the reorder depth.
- */
- void setReorderDepth(uint32_t depth);
-
- /**
- * Set the type of "key" to use in comparisons.
- */
- void setReorderKey(C2Config::ordinal_key_t key);
-
- /**
- * Return whether the output stash has any pending buffers.
- */
- bool hasPending() const;
-
- /**
- * Flush the stash and reset the depth and the key to their default values.
- */
- void clearStash();
-
- /**
- * Flush the stash.
- */
- void flushStash();
-
- /**
- * Push a buffer to the reorder stash.
- *
- * @param buffer C2Buffer object from the returned work.
- * @param notify Whether the returned work contains a buffer that should
- * be reported to the client. This may be false if the
- * caller wants to process the buffer without notifying the
- * client.
- * @param timestamp Buffer timestamp to report to the client.
- * @param flags Buffer flags to report to the client.
- * @param format Buffer format to report to the client.
- * @param ordinal Ordinal used in reordering. This determines when the
- * buffer will be popped from the output stash by
- * `popFromStashAndRegister()`.
- */
- void pushToStash(
- const std::shared_ptr<C2Buffer>& buffer,
- bool notify,
- int64_t timestamp,
- int32_t flags,
- const sp<AMessage>& format,
- const C2WorkOrdinalStruct& ordinal);
-
- enum BufferAction : int {
- SKIP,
- DISCARD,
- NOTIFY_CLIENT,
- REALLOCATE,
- RETRY,
- };
-
- /**
- * Try to atomically pop the first buffer from the reorder stash and
- * register it to an output slot. The function returns a value that
- * indicates a recommended course of action for the caller.
- *
- * If the stash is empty, the function will return `SKIP`.
- *
- * If the stash is not empty, the function will peek at the first (oldest)
- * entry in mPending process the buffer in the entry as follows:
- * - If the buffer should not be sent to the client, the function will
- * return `DISCARD`. The stash entry will be removed.
- * - If the buffer should be sent to the client, the function will attempt
- * to register the buffer to a slot. The registration may have 3 outcomes
- * corresponding to the following return values:
- * - `NOTIFY_CLIENT`: The buffer is successfully registered to a slot. The
- * output arguments @p index and @p outBuffer will contain valid values
- * that the caller can use to call onOutputBufferAvailable(). The stash
- * entry will be removed.
- * - `REALLOCATE`: The buffer is not registered because it is not
- * compatible with the current slots (which are available). The caller
- * should reallocate the OutputBuffers with slots that can fit the
- * returned @p c2Buffer. The stash entry will not be removed
- * - `RETRY`: All slots are currently occupied by the client. The caller
- * should try to call this function again after the client has released
- * some slots.
- *
- * @return What the caller should do afterwards.
- *
- * @param[out] c2Buffer Underlying C2Buffer associated to the first buffer
- * on the stash. This value is guaranteed to be valid
- * unless the return value is `SKIP`.
- * @param[out] index Slot index. This value is valid only if the return
- * value is `NOTIFY_CLIENT`.
- * @param[out] outBuffer Registered buffer. This value is valid only if the
- * return valu is `NOTIFY_CLIENT`.
- */
- BufferAction popFromStashAndRegister(
- std::shared_ptr<C2Buffer>* c2Buffer,
- size_t* index,
- sp<MediaCodecBuffer>* outBuffer);
-
-protected:
- sp<SkipCutBuffer> mSkipCutBuffer;
-
- /**
* Update the SkipCutBuffer object. No-op if it's never initialized.
*/
void updateSkipCutBuffer(int32_t sampleRate, int32_t channelCount);
@@ -380,8 +217,15 @@
*/
void submit(const sp<MediaCodecBuffer> &buffer);
+ /**
+ * Transfer SkipCutBuffer object to the other Buffers object.
+ */
+ void transferSkipCutBuffer(const sp<SkipCutBuffer> &scb);
+
+protected:
+ sp<SkipCutBuffer> mSkipCutBuffer;
+
private:
- // SkipCutBuffer
int32_t mDelay;
int32_t mPadding;
int32_t mSampleRate;
@@ -389,78 +233,7 @@
void setSkipCutBuffer(int32_t skip, int32_t cut);
- // Output stash
-
- // Output format that has not been made available to the client.
- sp<AMessage> mUnreportedFormat;
-
- // Struct for an entry in the output stash (mPending and mReorderStash)
- struct StashEntry {
- inline StashEntry()
- : buffer(nullptr),
- notify(false),
- timestamp(0),
- flags(0),
- format(),
- ordinal({0, 0, 0}) {}
- inline StashEntry(
- const std::shared_ptr<C2Buffer> &b,
- bool n,
- int64_t t,
- int32_t f,
- const sp<AMessage> &fmt,
- const C2WorkOrdinalStruct &o)
- : buffer(b),
- notify(n),
- timestamp(t),
- flags(f),
- format(fmt),
- ordinal(o) {}
- std::shared_ptr<C2Buffer> buffer;
- bool notify;
- int64_t timestamp;
- int32_t flags;
- sp<AMessage> format;
- C2WorkOrdinalStruct ordinal;
- };
-
- /**
- * FIFO queue of stash entries.
- */
- std::list<StashEntry> mPending;
- /**
- * Sorted list of stash entries.
- */
- std::list<StashEntry> mReorderStash;
- /**
- * Size limit of mReorderStash.
- */
- uint32_t mDepth{0};
- /**
- * Choice of key to use in ordering of stash entries in mReorderStash.
- */
- C2Config::ordinal_key_t mKey{C2Config::ORDINAL};
-
- /**
- * Return false if mPending is empty; otherwise, pop the first entry from
- * mPending and return true.
- */
- bool popPending(StashEntry *entry);
-
- /**
- * Push an entry as the first entry of mPending.
- */
- void deferPending(const StashEntry &entry);
-
- /**
- * Comparison of C2WorkOrdinalStruct based on mKey.
- */
- bool less(const C2WorkOrdinalStruct &o1,
- const C2WorkOrdinalStruct &o2) const;
-
DISALLOW_EVIL_CONSTRUCTORS(OutputBuffers);
-
- friend OutputBuffersArray;
};
/**
@@ -471,11 +244,9 @@
/**
* Create a new LocalBufferPool object.
*
- * \param poolCapacity max total size of buffers managed by this pool.
- *
* \return a newly created pool object.
*/
- static std::shared_ptr<LocalBufferPool> Create(size_t poolCapacity);
+ static std::shared_ptr<LocalBufferPool> Create();
/**
* Return an ABuffer object whose size is at least |capacity|.
@@ -907,8 +678,7 @@
class GraphicInputBuffers : public InputBuffers {
public:
- GraphicInputBuffers(
- size_t numInputSlots, const char *componentName, const char *name = "2D-BB-Input");
+ GraphicInputBuffers(const char *componentName, const char *name = "2D-BB-Input");
~GraphicInputBuffers() override = default;
bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override;
@@ -1000,7 +770,7 @@
bool isArrayMode() const final { return true; }
- std::unique_ptr<OutputBuffersArray> toArrayMode(size_t) final {
+ std::unique_ptr<OutputBuffers> toArrayMode(size_t) final {
return nullptr;
}
@@ -1039,12 +809,6 @@
*/
void grow(size_t newSize);
- /**
- * Transfer the SkipCutBuffer and the output stash from another
- * OutputBuffers.
- */
- void transferFrom(OutputBuffers* source);
-
private:
BuffersArrayImpl mImpl;
std::function<sp<Codec2Buffer>()> mAlloc;
@@ -1073,7 +837,7 @@
void flush(
const std::list<std::unique_ptr<C2Work>> &flushedWork) override;
- std::unique_ptr<OutputBuffersArray> toArrayMode(size_t size) override;
+ std::unique_ptr<OutputBuffers> toArrayMode(size_t size) override;
size_t numClientBuffers() const final;
@@ -1126,8 +890,7 @@
class RawGraphicOutputBuffers : public FlexOutputBuffers {
public:
- RawGraphicOutputBuffers(
- size_t numOutputSlots, const char *componentName, const char *name = "2D-BB-Output");
+ RawGraphicOutputBuffers(const char *componentName, const char *name = "2D-BB-Output");
~RawGraphicOutputBuffers() override = default;
sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) override;
diff --git a/media/codec2/sfplugin/Codec2Buffer.cpp b/media/codec2/sfplugin/Codec2Buffer.cpp
index 5b3a62f..25e7da9 100644
--- a/media/codec2/sfplugin/Codec2Buffer.cpp
+++ b/media/codec2/sfplugin/Codec2Buffer.cpp
@@ -18,6 +18,8 @@
#define LOG_TAG "Codec2Buffer"
#include <utils/Log.h>
+#include <android/hardware/cas/native/1.0/types.h>
+#include <android/hardware/drm/1.0/types.h>
#include <hidlmemory/FrameworkUtils.h>
#include <media/hardware/HardwareAPI.h>
#include <media/stagefright/CodecBase.h>
@@ -25,6 +27,7 @@
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/AUtils.h>
+#include <mediadrm/ICrypto.h>
#include <nativebase/nativebase.h>
#include <ui/Fence.h>
diff --git a/media/codec2/sfplugin/Codec2Buffer.h b/media/codec2/sfplugin/Codec2Buffer.h
index ff79946..dc788cd 100644
--- a/media/codec2/sfplugin/Codec2Buffer.h
+++ b/media/codec2/sfplugin/Codec2Buffer.h
@@ -20,16 +20,29 @@
#include <C2Buffer.h>
-#include <android/hardware/cas/native/1.0/types.h>
-#include <android/hardware/drm/1.0/types.h>
#include <binder/IMemory.h>
#include <media/hardware/VideoAPI.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/MediaCodecBuffer.h>
-#include <mediadrm/ICrypto.h>
namespace android {
+namespace hardware {
+class HidlMemory;
+namespace cas {
+namespace native {
+namespace V1_0 {
+struct SharedBuffer;
+} // namespace V1_0
+} // namespace native
+} // namespace cas
+namespace drm {
+namespace V1_0 {
+struct SharedBuffer;
+} // namespace V1_0
+} // namespace drm
+} // namespace hardware
+
/**
* Copies a graphic view into a media image.
*
diff --git a/media/codec2/sfplugin/tests/Android.bp b/media/codec2/sfplugin/tests/Android.bp
index fe5fa68..8d1a9c3 100644
--- a/media/codec2/sfplugin/tests/Android.bp
+++ b/media/codec2/sfplugin/tests/Android.bp
@@ -2,12 +2,13 @@
name: "ccodec_unit_test",
srcs: [
+ "CCodecBuffers_test.cpp",
"CCodecConfig_test.cpp",
"ReflectedParamUpdater_test.cpp",
],
defaults: [
- "libcodec2-hidl-defaults@1.0",
+ "libcodec2-impl-defaults",
"libcodec2-internal-defaults",
],
@@ -16,14 +17,24 @@
],
shared_libs: [
+ "android.hardware.media.bufferpool@2.0",
+ "android.hardware.media.c2@1.0",
"libcodec2",
"libcodec2_client",
+ "libhidlbase",
+ "libfmq",
+ "libmedia_omx",
"libsfplugin_ccodec",
"libsfplugin_ccodec_utils",
"libstagefright_foundation",
"libutils",
],
+ static_libs: [
+ "libcodec2_hidl@1.0",
+ "libstagefright_bufferpool@2.0",
+ ],
+
cflags: [
"-Werror",
"-Wall",
diff --git a/media/codec2/sfplugin/tests/CCodecBuffers_test.cpp b/media/codec2/sfplugin/tests/CCodecBuffers_test.cpp
new file mode 100644
index 0000000..5bee605
--- /dev/null
+++ b/media/codec2/sfplugin/tests/CCodecBuffers_test.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CCodecBuffers.h"
+
+#include <gtest/gtest.h>
+
+#include <media/stagefright/MediaCodecConstants.h>
+
+#include <C2PlatformSupport.h>
+
+namespace android {
+
+TEST(RawGraphicOutputBuffersTest, ChangeNumSlots) {
+ constexpr int32_t kWidth = 3840;
+ constexpr int32_t kHeight = 2160;
+
+ std::shared_ptr<RawGraphicOutputBuffers> buffers =
+ std::make_shared<RawGraphicOutputBuffers>("test");
+ sp<AMessage> format{new AMessage};
+ format->setInt32("width", kWidth);
+ format->setInt32("height", kHeight);
+ buffers->setFormat(format);
+
+ std::shared_ptr<C2BlockPool> pool;
+ ASSERT_EQ(OK, GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, nullptr, &pool));
+
+ // Register 4 buffers
+ std::vector<sp<MediaCodecBuffer>> clientBuffers;
+ auto registerBuffer = [&buffers, &clientBuffers, &pool] {
+ std::shared_ptr<C2GraphicBlock> block;
+ ASSERT_EQ(OK, pool->fetchGraphicBlock(
+ kWidth, kHeight, HAL_PIXEL_FORMAT_YCbCr_420_888,
+ C2MemoryUsage{C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block));
+ std::shared_ptr<C2Buffer> c2Buffer = C2Buffer::CreateGraphicBuffer(block->share(
+ block->crop(), C2Fence{}));
+ size_t index;
+ sp<MediaCodecBuffer> clientBuffer;
+ ASSERT_EQ(OK, buffers->registerBuffer(c2Buffer, &index, &clientBuffer));
+ ASSERT_NE(nullptr, clientBuffer);
+ while (clientBuffers.size() <= index) {
+ clientBuffers.emplace_back();
+ }
+ ASSERT_EQ(nullptr, clientBuffers[index]) << "index = " << index;
+ clientBuffers[index] = clientBuffer;
+ };
+ for (int i = 0; i < 4; ++i) {
+ registerBuffer();
+ }
+
+ // Release 2 buffers
+ auto releaseBuffer = [&buffers, &clientBuffers, kWidth, kHeight](int index) {
+ std::shared_ptr<C2Buffer> c2Buffer;
+ ASSERT_TRUE(buffers->releaseBuffer(clientBuffers[index], &c2Buffer))
+ << "index = " << index;
+ clientBuffers[index] = nullptr;
+ // Sanity checks
+ ASSERT_TRUE(c2Buffer->data().linearBlocks().empty());
+ ASSERT_EQ(1u, c2Buffer->data().graphicBlocks().size());
+ C2ConstGraphicBlock block = c2Buffer->data().graphicBlocks().front();
+ ASSERT_EQ(kWidth, block.width());
+ ASSERT_EQ(kHeight, block.height());
+ };
+ for (int i = 0, index = 0; i < 2 && index < clientBuffers.size(); ++index) {
+ if (clientBuffers[index] == nullptr) {
+ continue;
+ }
+ releaseBuffer(index);
+ ++i;
+ }
+
+ // Simulate # of slots 4->16
+ for (int i = 2; i < 16; ++i) {
+ registerBuffer();
+ }
+
+ // Release everything
+ for (int index = 0; index < clientBuffers.size(); ++index) {
+ if (clientBuffers[index] == nullptr) {
+ continue;
+ }
+ releaseBuffer(index);
+ }
+}
+
+} // namespace android
diff --git a/media/codec2/vndk/C2AllocatorGralloc.cpp b/media/codec2/vndk/C2AllocatorGralloc.cpp
index 3ac3d89..e1e1377 100644
--- a/media/codec2/vndk/C2AllocatorGralloc.cpp
+++ b/media/codec2/vndk/C2AllocatorGralloc.cpp
@@ -520,6 +520,22 @@
break;
}
+ case static_cast<uint32_t>(PixelFormat4::BLOB): {
+ void *pointer = nullptr;
+ // TODO: fence
+ status_t err = GraphicBufferMapper::get().lock(
+ const_cast<native_handle_t*>(mBuffer), grallocUsage,
+ { (int32_t)rect.left, (int32_t)rect.top,
+ (int32_t)rect.width, (int32_t)rect.height },
+ &pointer);
+ if (err) {
+ ALOGE("failed transaction: lock(BLOB)");
+ return C2_CORRUPTED;
+ }
+ *addr = (uint8_t *)pointer;
+ break;
+ }
+
case static_cast<uint32_t>(PixelFormat4::YCBCR_420_888):
// fall-through
case static_cast<uint32_t>(PixelFormat4::YV12):
diff --git a/media/extractors/fuzzers/Android.bp b/media/extractors/fuzzers/Android.bp
index 5cae39d..7bac4e1 100644
--- a/media/extractors/fuzzers/Android.bp
+++ b/media/extractors/fuzzers/Android.bp
@@ -112,6 +112,34 @@
}
cc_fuzz {
+ name: "amr_extractor_fuzzer",
+
+ srcs: [
+ "amr_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/amr",
+ ],
+
+ static_libs: [
+ "liblog",
+ "libstagefright_foundation",
+ "libmedia",
+ "libextractorfuzzerbase",
+ "libamrextractor",
+ ],
+
+ shared_libs: [
+ "libutils",
+ "libmediandk",
+ "libbinder",
+ ],
+
+ dictionary: "amr_extractor_fuzzer.dict",
+}
+
+cc_fuzz {
name: "mp3_extractor_fuzzer",
srcs: [
@@ -165,3 +193,35 @@
"libbinder",
],
}
+
+cc_fuzz {
+ name: "flac_extractor_fuzzer",
+
+ srcs: [
+ "flac_extractor_fuzzer.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/extractors/flac",
+ ],
+
+ static_libs: [
+ "liblog",
+ "libstagefright_foundation",
+ "libmedia",
+ "libextractorfuzzerbase",
+ "libstagefright_metadatautils",
+ "libFLAC",
+ "libflacextractor",
+ ],
+
+ shared_libs: [
+ "libutils",
+ "libmediandk",
+ "libbinder",
+ "libbinder_ndk",
+ "libbase",
+ ],
+
+ dictionary: "flac_extractor_fuzzer.dict",
+}
diff --git a/media/extractors/fuzzers/README.md b/media/extractors/fuzzers/README.md
index f09e1c2..96dd545 100644
--- a/media/extractors/fuzzers/README.md
+++ b/media/extractors/fuzzers/README.md
@@ -4,8 +4,10 @@
+ [libextractorfuzzerbase](#ExtractorFuzzerBase)
+ [libmp4extractor](#mp4ExtractorFuzzer)
+ [libwavextractor](#wavExtractorFuzzer)
++ [libamrextractor](#amrExtractorFuzzer)
+ [libmp3extractor](#mp3ExtractorFuzzer)
+ [libaacextractor](#aacExtractorFuzzer)
++ [libflacextractor](#flacExtractor)
# <a name="ExtractorFuzzerBase"></a> Fuzzer for libextractorfuzzerbase
All the extractors have a common API - creating a data source, extraction
@@ -81,6 +83,40 @@
$ adb shell /data/fuzz/arm64/wav_extractor_fuzzer/wav_extractor_fuzzer CORPUS_DIR
```
+# <a name="amrExtractorFuzzer"></a> Fuzzer for libamrextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for AMR extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the AMR extractor class.
+
+##### Maximize code coverage
+Dict file (dictionary file) is created for AMR to ensure that the required start
+bytes are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered.
+
+
+## Build
+
+This describes steps to build amr_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) amr_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some AMR files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/amr_extractor_fuzzer/amr_extractor_fuzzer CORPUS_DIR
+```
+
# <a name="mp3ExtractorFuzzer"></a> Fuzzer for libmp3extractor
## Plugin Design Considerations
@@ -139,6 +175,40 @@
$ adb shell /data/fuzz/arm64/aac_extractor_fuzzer/aac_extractor_fuzzer CORPUS_DIR
```
+# <a name="flacExtractor"></a> Fuzzer for libflacextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for FLAC extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the FLAC extractor object.
+
+##### Maximize code coverage
+Dict file (dictionary file) is created for FLAC to ensure that the required start
+bytes are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered.
+
+
+## Build
+
+This describes steps to build flac_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) flac_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some flac files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/flac_extractor_fuzzer/flac_extractor_fuzzer CORPUS_DIR
+```
+
## References:
* http://llvm.org/docs/LibFuzzer.html
* https://github.com/google/oss-fuzz
diff --git a/media/extractors/fuzzers/amr_extractor_fuzzer.cpp b/media/extractors/fuzzers/amr_extractor_fuzzer.cpp
new file mode 100644
index 0000000..b2f9261
--- /dev/null
+++ b/media/extractors/fuzzers/amr_extractor_fuzzer.cpp
@@ -0,0 +1,62 @@
+/******************************************************************************
+ *
+ * Copyright (C) 2020 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "AMRExtractor.h"
+
+using namespace android;
+
+class AmrExtractor : public ExtractorFuzzerBase {
+ public:
+ AmrExtractor() = default;
+ ~AmrExtractor() = default;
+
+ bool createExtractor();
+};
+
+bool AmrExtractor::createExtractor() {
+ mExtractor = new AMRExtractor(new DataSourceHelper(mDataSource->wrap()));
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ AmrExtractor* extractor = new AmrExtractor();
+ if (!extractor) {
+ return 0;
+ }
+ if (extractor->setDataSource(data, size)) {
+ if (extractor->createExtractor()) {
+ extractor->getExtractorDef();
+ extractor->getMetadata();
+ extractor->extractTracks();
+ extractor->getTracksMetadata();
+ }
+ }
+ delete extractor;
+ return 0;
+}
diff --git a/media/extractors/fuzzers/amr_extractor_fuzzer.dict b/media/extractors/fuzzers/amr_extractor_fuzzer.dict
new file mode 100644
index 0000000..bc5726c
--- /dev/null
+++ b/media/extractors/fuzzers/amr_extractor_fuzzer.dict
@@ -0,0 +1,2 @@
+# Start code
+kw1="#!AMR"
diff --git a/media/extractors/fuzzers/flac_extractor_fuzzer.cpp b/media/extractors/fuzzers/flac_extractor_fuzzer.cpp
new file mode 100644
index 0000000..61e41cf
--- /dev/null
+++ b/media/extractors/fuzzers/flac_extractor_fuzzer.cpp
@@ -0,0 +1,62 @@
+/******************************************************************************
+ *
+ * Copyright (C) 2020 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "FLACExtractor.h"
+
+using namespace android;
+
+class FlacExtractor : public ExtractorFuzzerBase {
+ public:
+ FlacExtractor() = default;
+ ~FlacExtractor() = default;
+
+ bool createExtractor();
+};
+
+bool FlacExtractor::createExtractor() {
+ mExtractor = new FLACExtractor(new DataSourceHelper(mDataSource->wrap()));
+ if (!mExtractor) {
+ return false;
+ }
+ mExtractor->name();
+ return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if ((!data) || (size == 0)) {
+ return 0;
+ }
+ FlacExtractor* extractor = new FlacExtractor();
+ if (!extractor) {
+ return 0;
+ }
+ if (extractor->setDataSource(data, size)) {
+ if (extractor->createExtractor()) {
+ extractor->getExtractorDef();
+ extractor->getMetadata();
+ extractor->extractTracks();
+ extractor->getTracksMetadata();
+ }
+ }
+ delete extractor;
+ return 0;
+}
diff --git a/media/extractors/fuzzers/flac_extractor_fuzzer.dict b/media/extractors/fuzzers/flac_extractor_fuzzer.dict
new file mode 100644
index 0000000..53ad44f
--- /dev/null
+++ b/media/extractors/fuzzers/flac_extractor_fuzzer.dict
@@ -0,0 +1,3 @@
+# Start code (bytes 0-3)
+# The below 4 bytes correspond to "fLaC" in ASCII
+kw1="\x66\x4C\x61\x43"
diff --git a/media/extractors/mp3/MP3Extractor.cpp b/media/extractors/mp3/MP3Extractor.cpp
index a838ae6..5165822 100644
--- a/media/extractors/mp3/MP3Extractor.cpp
+++ b/media/extractors/mp3/MP3Extractor.cpp
@@ -227,17 +227,17 @@
private:
static const size_t kMaxFrameSize;
- AMediaFormat *mMeta;
- DataSourceHelper *mDataSource;
- off64_t mFirstFramePos;
- uint32_t mFixedHeader;
- off64_t mCurrentPos;
- int64_t mCurrentTimeUs;
- bool mStarted;
- MP3Seeker *mSeeker;
+ AMediaFormat *mMeta = NULL;
+ DataSourceHelper *mDataSource = NULL;
+ off64_t mFirstFramePos = 0;
+ uint32_t mFixedHeader = 0;
+ off64_t mCurrentPos = 0;
+ int64_t mCurrentTimeUs = 0;
+ bool mStarted = false;
+ MP3Seeker *mSeeker = NULL;
- int64_t mBasisTimeUs;
- int64_t mSamplesRead;
+ int64_t mBasisTimeUs = 0;
+ int64_t mSamplesRead = 0;
MP3Source(const MP3Source &);
MP3Source &operator=(const MP3Source &);
@@ -251,11 +251,7 @@
MP3Extractor::MP3Extractor(
DataSourceHelper *source, Mp3Meta *meta)
- : mInitCheck(NO_INIT),
- mDataSource(source),
- mFirstFramePos(-1),
- mFixedHeader(0),
- mSeeker(NULL) {
+ : mDataSource(source) {
off64_t pos = 0;
off64_t post_id3_pos;
@@ -442,6 +438,7 @@
// (8000 samples/sec * 8 bits/byte)) + 1 padding byte/frame = 2881 bytes/frame.
// Set our max frame size to the nearest power of 2 above this size (aka, 4kB)
const size_t MP3Source::kMaxFrameSize = (1 << 12); /* 4096 bytes */
+
MP3Source::MP3Source(
AMediaFormat *meta, DataSourceHelper *source,
off64_t first_frame_pos, uint32_t fixed_header,
@@ -450,12 +447,7 @@
mDataSource(source),
mFirstFramePos(first_frame_pos),
mFixedHeader(fixed_header),
- mCurrentPos(0),
- mCurrentTimeUs(0),
- mStarted(false),
- mSeeker(seeker),
- mBasisTimeUs(0),
- mSamplesRead(0) {
+ mSeeker(seeker) {
}
MP3Source::~MP3Source() {
diff --git a/media/extractors/mp3/MP3Extractor.h b/media/extractors/mp3/MP3Extractor.h
index 1e38ab7..a2345da 100644
--- a/media/extractors/mp3/MP3Extractor.h
+++ b/media/extractors/mp3/MP3Extractor.h
@@ -45,13 +45,13 @@
virtual const char * name() { return "MP3Extractor"; }
private:
- status_t mInitCheck;
+ status_t mInitCheck = NO_INIT;
- DataSourceHelper *mDataSource;
- off64_t mFirstFramePos;
- AMediaFormat *mMeta;
- uint32_t mFixedHeader;
- MP3Seeker *mSeeker;
+ DataSourceHelper *mDataSource = NULL;
+ off64_t mFirstFramePos = -1;
+ AMediaFormat *mMeta = NULL;
+ uint32_t mFixedHeader = 0;
+ MP3Seeker *mSeeker = NULL;
MP3Extractor(const MP3Extractor &);
MP3Extractor &operator=(const MP3Extractor &);
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index edc09a9..a47f189 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -406,6 +406,7 @@
* Use this preset for capturing audio meant to be processed in real time
* and played back for live performance (e.g karaoke).
* The capture path will minimize latency and coupling with playback path.
+ * Available since API level 29.
*/
AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE = 10,
};
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 17d389e..0644368 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -106,21 +106,23 @@
}
void AudioStream::logOpen() {
- LOG_ALWAYS_FATAL_IF(mMetricsId.size() == 0, "mMetricsId is empty!");
- android::mediametrics::LogItem(mMetricsId)
- .set(AMEDIAMETRICS_PROP_PERFORMANCEMODE,
- AudioGlobal_convertPerformanceModeToText(getPerformanceMode()))
- .set(AMEDIAMETRICS_PROP_SHARINGMODE,
- AudioGlobal_convertSharingModeToText(getSharingMode()))
- .record();
+ if (mMetricsId.size() > 0) {
+ android::mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_PERFORMANCEMODE,
+ AudioGlobal_convertPerformanceModeToText(getPerformanceMode()))
+ .set(AMEDIAMETRICS_PROP_SHARINGMODE,
+ AudioGlobal_convertSharingModeToText(getSharingMode()))
+ .record();
+ }
}
void AudioStream::logBufferState() {
- LOG_ALWAYS_FATAL_IF(mMetricsId.size() == 0, "mMetricsId is empty!");
- android::mediametrics::LogItem(mMetricsId)
- .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t) getBufferSize())
- .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getXRunCount())
- .record();
+ if (mMetricsId.size() > 0) {
+ android::mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t) getBufferSize())
+ .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getXRunCount())
+ .record();
+ }
}
aaudio_result_t AudioStream::systemStart() {
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index f888752..8a6a5dc 100644
--- a/media/libmedia/Android.bp
+++ b/media/libmedia/Android.bp
@@ -15,6 +15,18 @@
],
}
+cc_library_headers {
+ name: "libmedia_datasource_headers",
+ export_include_dirs: ["include"],
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ apex_available: ["com.android.media"],
+}
+
filegroup {
name: "libmedia_omx_aidl",
srcs: [
diff --git a/media/libmedia/aidl/android/media/IResourceManagerService.aidl b/media/libmedia/aidl/android/media/IResourceManagerService.aidl
index 3dd0859..1b2d522 100644
--- a/media/libmedia/aidl/android/media/IResourceManagerService.aidl
+++ b/media/libmedia/aidl/android/media/IResourceManagerService.aidl
@@ -94,4 +94,12 @@
* remove existing override on originalPid if newPid is -1.
*/
void overridePid(int originalPid, int newPid);
+
+ /**
+ * Mark a client for pending removal
+ *
+ * @param pid pid from which the client's resources will be removed.
+ * @param clientId clientId within the pid that will be removed.
+ */
+ void markClientForPendingRemoval(int pid, long clientId);
}
diff --git a/media/libmediametrics/include/MediaMetricsConstants.h b/media/libmediametrics/include/MediaMetricsConstants.h
index 00da69a..ec0e133 100644
--- a/media/libmediametrics/include/MediaMetricsConstants.h
+++ b/media/libmediametrics/include/MediaMetricsConstants.h
@@ -37,6 +37,11 @@
// They must be appended with another value to make a key.
#define AMEDIAMETRICS_KEY_PREFIX_AUDIO "audio."
+// The AudioMmap key appends the "trackId" to the prefix.
+// This is the AudioFlinger equivalent of the AAudio Stream.
+// TODO: unify with AMEDIAMETRICS_KEY_PREFIX_AUDIO_STREAM
+#define AMEDIAMETRICS_KEY_PREFIX_AUDIO_MMAP AMEDIAMETRICS_KEY_PREFIX_AUDIO "mmap."
+
// The AudioRecord key appends the "trackId" to the prefix.
#define AMEDIAMETRICS_KEY_PREFIX_AUDIO_RECORD AMEDIAMETRICS_KEY_PREFIX_AUDIO "record."
@@ -95,6 +100,14 @@
#define AMEDIAMETRICS_PROP_CHANNELCOUNT "channelCount" // int32
#define AMEDIAMETRICS_PROP_CHANNELMASK "channelMask" // int32
#define AMEDIAMETRICS_PROP_CONTENTTYPE "contentType" // string attributes (AudioTrack)
+#define AMEDIAMETRICS_PROP_CUMULATIVETIMENS "cumulativeTimeNs" // int64_t playback/record time
+ // since start
+// DEVICE values are averaged since starting on device
+#define AMEDIAMETRICS_PROP_DEVICELATENCYMS "deviceLatencyMs" // double - avg latency time
+#define AMEDIAMETRICS_PROP_DEVICESTARTUPMS "deviceStartupMs" // double - avg startup time
+#define AMEDIAMETRICS_PROP_DEVICETIMENS "deviceTimeNs" // int64_t playback/record time
+#define AMEDIAMETRICS_PROP_DEVICEVOLUME "deviceVolume" // double - average device volume
+
#define AMEDIAMETRICS_PROP_DIRECTION "direction" // string AAudio input or output
#define AMEDIAMETRICS_PROP_DURATIONNS "durationNs" // int64 duration time span
#define AMEDIAMETRICS_PROP_ENCODING "encoding" // string value of format
@@ -105,7 +118,9 @@
#define AMEDIAMETRICS_PROP_FRAMECOUNT "frameCount" // int32
#define AMEDIAMETRICS_PROP_INPUTDEVICES "inputDevices" // string value
+#define AMEDIAMETRICS_PROP_INTERVALCOUNT "intervalCount" // int32
#define AMEDIAMETRICS_PROP_LATENCYMS "latencyMs" // double value
+#define AMEDIAMETRICS_PROP_NAME "name" // string value
#define AMEDIAMETRICS_PROP_ORIGINALFLAGS "originalFlags" // int32
#define AMEDIAMETRICS_PROP_OUTPUTDEVICES "outputDevices" // string value
#define AMEDIAMETRICS_PROP_PERFORMANCEMODE "performanceMode" // string value, "none", lowLatency"
@@ -129,6 +144,7 @@
#define AMEDIAMETRICS_PROP_TRACKID "trackId" // int32 port id of track/record
#define AMEDIAMETRICS_PROP_TYPE "type" // string (thread type)
#define AMEDIAMETRICS_PROP_UNDERRUN "underrun" // int32
+#define AMEDIAMETRICS_PROP_UNDERRUNFRAMES "underrunFrames" // int64_t from Thread
#define AMEDIAMETRICS_PROP_USAGE "usage" // string attributes (ATrack)
#define AMEDIAMETRICS_PROP_VOLUME_LEFT "volume.left" // double (AudioTrack)
#define AMEDIAMETRICS_PROP_VOLUME_RIGHT "volume.right" // double (AudioTrack)
@@ -140,12 +156,14 @@
// Values are strings accepted for a given property.
// An event is a general description, which often is a function name.
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_BEGINAUDIOINTERVALGROUP "beginAudioIntervalGroup"
#define AMEDIAMETRICS_PROP_EVENT_VALUE_CLOSE "close"
#define AMEDIAMETRICS_PROP_EVENT_VALUE_CREATE "create"
#define AMEDIAMETRICS_PROP_EVENT_VALUE_CREATEAUDIOPATCH "createAudioPatch"
#define AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR "ctor"
#define AMEDIAMETRICS_PROP_EVENT_VALUE_DISCONNECT "disconnect"
#define AMEDIAMETRICS_PROP_EVENT_VALUE_DTOR "dtor"
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP "endAudioIntervalGroup"
#define AMEDIAMETRICS_PROP_EVENT_VALUE_FLUSH "flush" // AudioTrack
#define AMEDIAMETRICS_PROP_EVENT_VALUE_INVALIDATE "invalidate" // server track, record
#define AMEDIAMETRICS_PROP_EVENT_VALUE_OPEN "open"
diff --git a/media/libmediaplayerservice/Android.bp b/media/libmediaplayerservice/Android.bp
index 902d846..324f4ae 100644
--- a/media/libmediaplayerservice/Android.bp
+++ b/media/libmediaplayerservice/Android.bp
@@ -16,7 +16,6 @@
"android.hardware.media.c2@1.0",
"android.hardware.media.omx@1.0",
"libbase",
- "libandroid",
"libandroid_net",
"libaudioclient",
"libbinder",
diff --git a/media/libmediaplayerservice/nuplayer/Android.bp b/media/libmediaplayerservice/nuplayer/Android.bp
index 7206eab..f5e44c7 100644
--- a/media/libmediaplayerservice/nuplayer/Android.bp
+++ b/media/libmediaplayerservice/nuplayer/Android.bp
@@ -48,8 +48,6 @@
},
shared_libs: [
- "libandroid",
- "libandroid_net",
"libbinder",
"libdatasource",
"libui",
diff --git a/media/libmediatranscoding/Android.bp b/media/libmediatranscoding/Android.bp
index a2c1af7..431f0be 100644
--- a/media/libmediatranscoding/Android.bp
+++ b/media/libmediatranscoding/Android.bp
@@ -45,6 +45,7 @@
srcs: [
"TranscodingClientManager.cpp",
"TranscodingJobScheduler.cpp",
+ "TranscodingUidPolicy.cpp",
],
shared_libs: [
@@ -53,6 +54,7 @@
"liblog",
"libutils",
"libmediatranscoder",
+ "libbinder",
],
export_include_dirs: ["include"],
diff --git a/media/libmediatranscoding/TranscodingClientManager.cpp b/media/libmediatranscoding/TranscodingClientManager.cpp
index bfa08a2..fd87cb4 100644
--- a/media/libmediatranscoding/TranscodingClientManager.cpp
+++ b/media/libmediatranscoding/TranscodingClientManager.cpp
@@ -43,7 +43,8 @@
* object doesn't get created again, otherwise the binder object pointer
* may not be unique.
*/
- SpAIBinder mClientCallback;
+ SpAIBinder mClientBinder;
+ std::shared_ptr<ITranscodingClientCallback> mClientCallback;
/* A unique id assigned to the client by the service. This number is used
* by the service for indexing. Here we use the binder object's pointer
* (casted to int64t_t) as the client id.
@@ -78,8 +79,9 @@
const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid, uid_t uid,
const std::string& clientName, const std::string& opPackageName,
TranscodingClientManager* owner)
- : mClientCallback((callback != nullptr) ? callback->asBinder() : nullptr),
- mClientId((int64_t)mClientCallback.get()),
+ : mClientBinder((callback != nullptr) ? callback->asBinder() : nullptr),
+ mClientCallback(callback),
+ mClientId((int64_t)mClientBinder.get()),
mClientPid(pid),
mClientUid(uid),
mClientName(clientName),
@@ -98,9 +100,8 @@
int32_t jobId = mNextJobId.fetch_add(1);
- *_aidl_return =
- mOwner->mJobScheduler->submit(mClientId, jobId, mClientPid, in_request,
- ITranscodingClientCallback::fromBinder(mClientCallback));
+ *_aidl_return = mOwner->mJobScheduler->submit(mClientId, jobId, mClientUid, in_request,
+ mClientCallback);
if (*_aidl_return) {
out_job->jobId = jobId;
@@ -205,7 +206,7 @@
(long long)client->mClientId, client->mClientPid, client->mClientUid,
client->mClientName.c_str(), client->mClientOpPackageName.c_str());
- AIBinder_linkToDeath(client->mClientCallback.get(), mDeathRecipient.get(),
+ AIBinder_linkToDeath(client->mClientBinder.get(), mDeathRecipient.get(),
reinterpret_cast<void*>(client.get()));
// Adds the new client to the map.
@@ -227,7 +228,7 @@
return INVALID_OPERATION;
}
- SpAIBinder callback = it->second->mClientCallback;
+ SpAIBinder callback = it->second->mClientBinder;
// Check if the client still live. If alive, unlink the death.
if (callback.get() != nullptr) {
diff --git a/media/libmediatranscoding/TranscodingJobScheduler.cpp b/media/libmediatranscoding/TranscodingJobScheduler.cpp
index 9dd070c..83d7a82 100644
--- a/media/libmediatranscoding/TranscodingJobScheduler.cpp
+++ b/media/libmediatranscoding/TranscodingJobScheduler.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-// #define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
#define LOG_TAG "TranscodingJobScheduler"
#define VALIDATE_STATE 1
@@ -27,7 +27,7 @@
namespace android {
-constexpr static pid_t OFFLINE_PID = -1;
+constexpr static uid_t OFFLINE_UID = -1;
//static
String8 TranscodingJobScheduler::jobToString(const JobKeyType& jobKey) {
@@ -36,12 +36,12 @@
TranscodingJobScheduler::TranscodingJobScheduler(
const std::shared_ptr<TranscoderInterface>& transcoder,
- const std::shared_ptr<ProcessInfoInterface>& procInfo)
- : mTranscoder(transcoder), mProcInfo(procInfo), mCurrentJob(nullptr), mResourceLost(false) {
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy)
+ : mTranscoder(transcoder), mUidPolicy(uidPolicy), mCurrentJob(nullptr), mResourceLost(false) {
// Only push empty offline queue initially. Realtime queues are added when requests come in.
- mPidSortedList.push_back(OFFLINE_PID);
- mOfflinePidIterator = mPidSortedList.begin();
- mJobQueues.emplace(OFFLINE_PID, JobQueueType());
+ mUidSortedList.push_back(OFFLINE_UID);
+ mOfflineUidIterator = mUidSortedList.begin();
+ mJobQueues.emplace(OFFLINE_UID, JobQueueType());
}
TranscodingJobScheduler::~TranscodingJobScheduler() {}
@@ -50,8 +50,8 @@
if (mJobMap.empty()) {
return nullptr;
}
- pid_t topPid = *mPidSortedList.begin();
- JobKeyType topJobKey = *mJobQueues[topPid].begin();
+ uid_t topUid = *mUidSortedList.begin();
+ JobKeyType topJobKey = *mJobQueues[topUid].begin();
return &mJobMap[topJobKey];
}
@@ -92,20 +92,24 @@
return;
}
- // Remove job from pid's queue.
- const pid_t pid = mJobMap[jobKey].pid;
- JobQueueType& jobQueue = mJobQueues[pid];
+ // Remove job from uid's queue.
+ const uid_t uid = mJobMap[jobKey].uid;
+ JobQueueType& jobQueue = mJobQueues[uid];
auto it = std::find(jobQueue.begin(), jobQueue.end(), jobKey);
if (it == jobQueue.end()) {
- ALOGE("couldn't find job %s in queue for pid %d", jobToString(jobKey).c_str(), pid);
+ ALOGE("couldn't find job %s in queue for uid %d", jobToString(jobKey).c_str(), uid);
return;
}
jobQueue.erase(it);
- // If this is the last job in a real-time queue, remove this pid's queue.
- if (pid != OFFLINE_PID && jobQueue.empty()) {
- mPidSortedList.remove(pid);
- mJobQueues.erase(pid);
+ // If this is the last job in a real-time queue, remove this uid's queue.
+ if (uid != OFFLINE_UID && jobQueue.empty()) {
+ mUidSortedList.remove(uid);
+ mJobQueues.erase(uid);
+ mUidPolicy->unregisterMonitorUid(uid);
+
+ std::unordered_set<uid_t> topUids = mUidPolicy->getTopUids();
+ moveUidsToTop_l(topUids, false /*preserveTopUid*/);
}
// Clear current job.
@@ -117,12 +121,65 @@
mJobMap.erase(jobKey);
}
-bool TranscodingJobScheduler::submit(ClientIdType clientId, int32_t jobId, pid_t pid,
+/**
+ * Moves the set of uids to the front of mUidSortedList (which is used to pick
+ * the next job to run).
+ *
+ * This is called when 1) we received a onTopUidsChanged() callbcak from UidPolicy,
+ * or 2) we removed the job queue for a uid because it becomes empty.
+ *
+ * In case of 1), if there are multiple uids in the set, and the current front
+ * uid in mUidSortedList is still in the set, we try to keep that uid at front
+ * so that current job run is not interrupted. (This is not a concern for case 2)
+ * because the queue for a uid was just removed entirely.)
+ */
+void TranscodingJobScheduler::moveUidsToTop_l(const std::unordered_set<uid_t>& uids,
+ bool preserveTopUid) {
+ // If uid set is empty, nothing to do. Do not change the queue status.
+ if (uids.empty()) {
+ return;
+ }
+
+ // Save the current top uid.
+ uid_t curTopUid = *mUidSortedList.begin();
+ bool pushCurTopToFront = false;
+ int32_t numUidsMoved = 0;
+
+ // Go through the sorted uid list once, and move the ones in top set to front.
+ for (auto it = mUidSortedList.begin(); it != mUidSortedList.end();) {
+ uid_t uid = *it;
+
+ if (uid != OFFLINE_UID && uids.count(uid) > 0) {
+ it = mUidSortedList.erase(it);
+
+ // If this is the top we're preserving, don't push it here, push
+ // it after the for-loop.
+ if (uid == curTopUid && preserveTopUid) {
+ pushCurTopToFront = true;
+ } else {
+ mUidSortedList.push_front(uid);
+ }
+
+ // If we found all uids in the set, break out.
+ if (++numUidsMoved == uids.size()) {
+ break;
+ }
+ } else {
+ ++it;
+ }
+ }
+
+ if (pushCurTopToFront) {
+ mUidSortedList.push_front(curTopUid);
+ }
+}
+
+bool TranscodingJobScheduler::submit(ClientIdType clientId, int32_t jobId, uid_t uid,
const TranscodingRequestParcel& request,
const std::weak_ptr<ITranscodingClientCallback>& callback) {
JobKeyType jobKey = std::make_pair(clientId, jobId);
- ALOGV("%s: job %s, pid %d, prioirty %d", __FUNCTION__, jobToString(jobKey).c_str(), pid,
+ ALOGV("%s: job %s, uid %d, prioirty %d", __FUNCTION__, jobToString(jobKey).c_str(), uid,
(int32_t)request.priority);
std::scoped_lock lock{mLock};
@@ -135,37 +192,38 @@
// TODO(chz): only support offline vs real-time for now. All kUnspecified jobs
// go to offline queue.
if (request.priority == TranscodingJobPriority::kUnspecified) {
- pid = OFFLINE_PID;
+ uid = OFFLINE_UID;
}
// Add job to job map.
mJobMap[jobKey].key = jobKey;
- mJobMap[jobKey].pid = pid;
+ mJobMap[jobKey].uid = uid;
mJobMap[jobKey].state = Job::NOT_STARTED;
mJobMap[jobKey].request = request;
mJobMap[jobKey].callback = callback;
// If it's an offline job, the queue was already added in constructor.
- // If it's a real-time jobs, check if a queue is already present for the pid,
+ // If it's a real-time jobs, check if a queue is already present for the uid,
// and add a new queue if needed.
- if (pid != OFFLINE_PID) {
- if (mJobQueues.count(pid) == 0) {
- if (mProcInfo->isProcessOnTop(pid)) {
- mPidSortedList.push_front(pid);
+ if (uid != OFFLINE_UID) {
+ if (mJobQueues.count(uid) == 0) {
+ mUidPolicy->registerMonitorUid(uid);
+ if (mUidPolicy->isUidOnTop(uid)) {
+ mUidSortedList.push_front(uid);
} else {
// Shouldn't be submitting real-time requests from non-top app,
// put it in front of the offline queue.
- mPidSortedList.insert(mOfflinePidIterator, pid);
+ mUidSortedList.insert(mOfflineUidIterator, uid);
}
- } else if (pid != *mPidSortedList.begin()) {
- if (mProcInfo->isProcessOnTop(pid)) {
- mPidSortedList.remove(pid);
- mPidSortedList.push_front(pid);
+ } else if (uid != *mUidSortedList.begin()) {
+ if (mUidPolicy->isUidOnTop(uid)) {
+ mUidSortedList.remove(uid);
+ mUidSortedList.push_front(uid);
}
}
}
- // Append this job to the pid's queue.
- mJobQueues[pid].push_back(jobKey);
+ // Append this job to the uid's queue.
+ mJobQueues[uid].push_back(jobKey);
updateCurrentJob_l();
@@ -222,7 +280,7 @@
std::scoped_lock lock{mLock};
if (mJobMap.count(jobKey) == 0) {
- ALOGW("ignoring abort for non-existent job");
+ ALOGW("ignoring finish for non-existent job");
return;
}
@@ -230,7 +288,7 @@
// to client if the job is paused. Transcoder could have posted finish when
// we're pausing it, and the finish arrived after we changed current job.
if (mJobMap[jobKey].state == Job::NOT_STARTED) {
- ALOGW("ignoring abort for job that was never started");
+ ALOGW("ignoring finish for job that was never started");
return;
}
@@ -258,7 +316,7 @@
std::scoped_lock lock{mLock};
if (mJobMap.count(jobKey) == 0) {
- ALOGW("ignoring abort for non-existent job");
+ ALOGW("ignoring error for non-existent job");
return;
}
@@ -266,7 +324,7 @@
// to client if the job is paused. Transcoder could have posted finish when
// we're pausing it, and the finish arrived after we changed current job.
if (mJobMap[jobKey].state == Job::NOT_STARTED) {
- ALOGW("ignoring abort for job that was never started");
+ ALOGW("ignoring error for job that was never started");
return;
}
@@ -286,6 +344,34 @@
validateState_l();
}
+void TranscodingJobScheduler::onProgressUpdate(int64_t clientId, int32_t jobId, int32_t progress) {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ ALOGV("%s: job %s, progress %d", __FUNCTION__, jobToString(jobKey).c_str(), progress);
+
+ std::scoped_lock lock{mLock};
+
+ if (mJobMap.count(jobKey) == 0) {
+ ALOGW("ignoring progress for non-existent job");
+ return;
+ }
+
+ // Only ignore if job was never started. In particular, propagate the status
+ // to client if the job is paused. Transcoder could have posted finish when
+ // we're pausing it, and the finish arrived after we changed current job.
+ if (mJobMap[jobKey].state == Job::NOT_STARTED) {
+ ALOGW("ignoring progress for job that was never started");
+ return;
+ }
+
+ {
+ auto clientCallback = mJobMap[jobKey].callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onProgressUpdate(jobId, progress);
+ }
+ }
+}
+
void TranscodingJobScheduler::onResourceLost() {
ALOGV("%s", __FUNCTION__);
@@ -302,28 +388,25 @@
validateState_l();
}
-void TranscodingJobScheduler::onTopProcessChanged(pid_t pid) {
- ALOGV("%s: pid %d", __FUNCTION__, pid);
+void TranscodingJobScheduler::onTopUidsChanged(const std::unordered_set<uid_t>& uids) {
+ if (uids.empty()) {
+ ALOGW("%s: ignoring empty uids", __FUNCTION__);
+ return;
+ }
+
+ std::string uidStr;
+ for (auto it = uids.begin(); it != uids.end(); it++) {
+ if (!uidStr.empty()) {
+ uidStr += ", ";
+ }
+ uidStr += std::to_string(*it);
+ }
+
+ ALOGD("%s: topUids: size %zu, uids: %s", __FUNCTION__, uids.size(), uidStr.c_str());
std::scoped_lock lock{mLock};
- if (pid < 0) {
- ALOGW("bringProcessToTop: ignoring invalid pid %d", pid);
- return;
- }
- // If this pid doesn't have any jobs, we don't care about it.
- if (mJobQueues.count(pid) == 0) {
- ALOGW("bringProcessToTop: ignoring pid %d without any jobs", pid);
- return;
- }
- // If this pid is already top, don't do anything.
- if (pid == *mPidSortedList.begin()) {
- ALOGW("pid %d is already top", pid);
- return;
- }
-
- mPidSortedList.remove(pid);
- mPidSortedList.push_front(pid);
+ moveUidsToTop_l(uids, true /*preserveTopUid*/);
updateCurrentJob_l();
@@ -343,26 +426,26 @@
void TranscodingJobScheduler::validateState_l() {
#ifdef VALIDATE_STATE
- LOG_ALWAYS_FATAL_IF(mJobQueues.count(OFFLINE_PID) != 1,
+ LOG_ALWAYS_FATAL_IF(mJobQueues.count(OFFLINE_UID) != 1,
"mJobQueues offline queue number is not 1");
- LOG_ALWAYS_FATAL_IF(*mOfflinePidIterator != OFFLINE_PID,
- "mOfflinePidIterator not pointing to offline pid");
- LOG_ALWAYS_FATAL_IF(mPidSortedList.size() != mJobQueues.size(),
- "mPidList and mJobQueues size mismatch");
+ LOG_ALWAYS_FATAL_IF(*mOfflineUidIterator != OFFLINE_UID,
+ "mOfflineUidIterator not pointing to offline uid");
+ LOG_ALWAYS_FATAL_IF(mUidSortedList.size() != mJobQueues.size(),
+ "mUidList and mJobQueues size mismatch");
int32_t totalJobs = 0;
- for (auto pidIt = mPidSortedList.begin(); pidIt != mPidSortedList.end(); pidIt++) {
- LOG_ALWAYS_FATAL_IF(mJobQueues.count(*pidIt) != 1, "mJobQueues count for pid %d is not 1",
- *pidIt);
- for (auto jobIt = mJobQueues[*pidIt].begin(); jobIt != mJobQueues[*pidIt].end(); jobIt++) {
+ for (auto uidIt = mUidSortedList.begin(); uidIt != mUidSortedList.end(); uidIt++) {
+ LOG_ALWAYS_FATAL_IF(mJobQueues.count(*uidIt) != 1, "mJobQueues count for uid %d is not 1",
+ *uidIt);
+ for (auto jobIt = mJobQueues[*uidIt].begin(); jobIt != mJobQueues[*uidIt].end(); jobIt++) {
LOG_ALWAYS_FATAL_IF(mJobMap.count(*jobIt) != 1, "mJobs count for job %s is not 1",
jobToString(*jobIt).c_str());
}
- totalJobs += mJobQueues[*pidIt].size();
+ totalJobs += mJobQueues[*uidIt].size();
}
LOG_ALWAYS_FATAL_IF(mJobMap.size() != totalJobs,
- "mJobs size doesn't match total jobs counted from pid queues");
+ "mJobs size doesn't match total jobs counted from uid queues");
#endif // VALIDATE_STATE
}
diff --git a/media/libmediatranscoding/TranscodingUidPolicy.cpp b/media/libmediatranscoding/TranscodingUidPolicy.cpp
new file mode 100644
index 0000000..9c8d3fe
--- /dev/null
+++ b/media/libmediatranscoding/TranscodingUidPolicy.cpp
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2020 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 "TranscodingUidPolicy"
+
+#include <binder/ActivityManager.h>
+#include <cutils/misc.h> // FIRST_APPLICATION_UID
+#include <inttypes.h>
+#include <media/TranscodingUidPolicy.h>
+#include <utils/Log.h>
+
+#include <utility>
+
+namespace android {
+
+constexpr static uid_t OFFLINE_UID = -1;
+constexpr static const char* kTranscodingTag = "transcoding";
+
+struct TranscodingUidPolicy::UidObserver : public BnUidObserver,
+ public virtual IBinder::DeathRecipient {
+ explicit UidObserver(TranscodingUidPolicy* owner) : mOwner(owner) {}
+
+ // IUidObserver
+ void onUidGone(uid_t uid, bool disabled) override;
+ void onUidActive(uid_t uid) override;
+ void onUidIdle(uid_t uid, bool disabled) override;
+ void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq,
+ int32_t capability) override;
+
+ // IBinder::DeathRecipient implementation
+ void binderDied(const wp<IBinder>& who) override;
+
+ TranscodingUidPolicy* mOwner;
+};
+
+void TranscodingUidPolicy::UidObserver::onUidGone(uid_t uid __unused, bool disabled __unused) {}
+
+void TranscodingUidPolicy::UidObserver::onUidActive(uid_t uid __unused) {}
+
+void TranscodingUidPolicy::UidObserver::onUidIdle(uid_t uid __unused, bool disabled __unused) {}
+
+void TranscodingUidPolicy::UidObserver::onUidStateChanged(uid_t uid, int32_t procState,
+ int64_t procStateSeq __unused,
+ int32_t capability __unused) {
+ mOwner->onUidStateChanged(uid, procState);
+}
+
+void TranscodingUidPolicy::UidObserver::binderDied(const wp<IBinder>& /*who*/) {
+ ALOGW("TranscodingUidPolicy: ActivityManager has died");
+ // TODO(chz): this is a rare event (since if the AMS is dead, the system is
+ // probably dead as well). But we should try to reconnect.
+ mOwner->setUidObserverRegistered(false);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+TranscodingUidPolicy::TranscodingUidPolicy()
+ : mAm(std::make_shared<ActivityManager>()),
+ mUidObserver(new UidObserver(this)),
+ mRegistered(false),
+ mTopUidState(ActivityManager::PROCESS_STATE_UNKNOWN) {
+ registerSelf();
+}
+
+TranscodingUidPolicy::~TranscodingUidPolicy() {
+ unregisterSelf();
+}
+
+void TranscodingUidPolicy::registerSelf() {
+ status_t res = mAm->linkToDeath(mUidObserver.get());
+ mAm->registerUidObserver(
+ mUidObserver.get(),
+ ActivityManager::UID_OBSERVER_GONE | ActivityManager::UID_OBSERVER_IDLE |
+ ActivityManager::UID_OBSERVER_ACTIVE | ActivityManager::UID_OBSERVER_PROCSTATE,
+ ActivityManager::PROCESS_STATE_UNKNOWN, String16(kTranscodingTag));
+
+ if (res == OK) {
+ Mutex::Autolock _l(mUidLock);
+
+ mRegistered = true;
+ ALOGI("TranscodingUidPolicy: Registered with ActivityManager");
+ } else {
+ mAm->unregisterUidObserver(mUidObserver.get());
+ }
+}
+
+void TranscodingUidPolicy::unregisterSelf() {
+ mAm->unregisterUidObserver(mUidObserver.get());
+ mAm->unlinkToDeath(mUidObserver.get());
+
+ Mutex::Autolock _l(mUidLock);
+
+ mRegistered = false;
+
+ ALOGI("TranscodingUidPolicy: Unregistered with ActivityManager");
+}
+
+void TranscodingUidPolicy::setUidObserverRegistered(bool registered) {
+ Mutex::Autolock _l(mUidLock);
+
+ mRegistered = registered;
+}
+
+void TranscodingUidPolicy::setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) {
+ mUidPolicyCallback = cb;
+}
+
+void TranscodingUidPolicy::registerMonitorUid(uid_t uid) {
+ Mutex::Autolock _l(mUidLock);
+ if (uid == OFFLINE_UID) {
+ ALOGW("Ignoring the offline uid");
+ return;
+ }
+ if (mUidStateMap.find(uid) != mUidStateMap.end()) {
+ ALOGE("%s: Trying to register uid: %d which is already monitored!", __FUNCTION__, uid);
+ return;
+ }
+
+ int32_t state = ActivityManager::PROCESS_STATE_UNKNOWN;
+ if (mRegistered && mAm->isUidActiveOrForeground(uid, String16(kTranscodingTag))) {
+ state = mAm->getUidProcessState(uid, String16(kTranscodingTag));
+ }
+
+ ALOGV("%s: inserting new uid: %u, procState %d", __FUNCTION__, uid, state);
+
+ mUidStateMap.emplace(std::pair<uid_t, int32_t>(uid, state));
+ mStateUidMap[state].insert(uid);
+
+ updateTopUid_l();
+}
+
+void TranscodingUidPolicy::unregisterMonitorUid(uid_t uid) {
+ Mutex::Autolock _l(mUidLock);
+
+ auto it = mUidStateMap.find(uid);
+ if (it == mUidStateMap.end()) {
+ ALOGE("%s: Trying to unregister uid: %d which is not monitored!", __FUNCTION__, uid);
+ return;
+ }
+
+ auto stateIt = mStateUidMap.find(it->second);
+ if (stateIt != mStateUidMap.end()) {
+ stateIt->second.erase(uid);
+ if (stateIt->second.empty()) {
+ mStateUidMap.erase(stateIt);
+ }
+ }
+ mUidStateMap.erase(it);
+
+ updateTopUid_l();
+}
+
+bool TranscodingUidPolicy::isUidOnTop(uid_t uid) {
+ Mutex::Autolock _l(mUidLock);
+
+ return mTopUidState != ActivityManager::PROCESS_STATE_UNKNOWN &&
+ mTopUidState == getProcState_l(uid);
+}
+
+std::unordered_set<uid_t> TranscodingUidPolicy::getTopUids() const {
+ Mutex::Autolock _l(mUidLock);
+
+ if (mTopUidState == ActivityManager::PROCESS_STATE_UNKNOWN) {
+ return std::unordered_set<uid_t>();
+ }
+
+ return mStateUidMap.at(mTopUidState);
+}
+
+void TranscodingUidPolicy::onUidStateChanged(uid_t uid, int32_t procState) {
+ ALOGV("onUidStateChanged: %u, procState %d", uid, procState);
+
+ bool topUidSetChanged = false;
+ std::unordered_set<uid_t> topUids;
+ {
+ Mutex::Autolock _l(mUidLock);
+ auto it = mUidStateMap.find(uid);
+ if (it != mUidStateMap.end() && it->second != procState) {
+ // Top set changed if 1) the uid is in the current top uid set, or 2) the
+ // new procState is at least the same priority as the current top uid state.
+ bool isUidCurrentTop = mTopUidState != ActivityManager::PROCESS_STATE_UNKNOWN &&
+ mStateUidMap[mTopUidState].count(uid) > 0;
+ bool isNewStateHigherThanTop = procState != ActivityManager::PROCESS_STATE_UNKNOWN &&
+ (procState <= mTopUidState ||
+ mTopUidState == ActivityManager::PROCESS_STATE_UNKNOWN);
+ topUidSetChanged = (isUidCurrentTop || isNewStateHigherThanTop);
+
+ // Move uid to the new procState.
+ mStateUidMap[it->second].erase(uid);
+ mStateUidMap[procState].insert(uid);
+ it->second = procState;
+
+ if (topUidSetChanged) {
+ updateTopUid_l();
+
+ // Make a copy of the uid set for callback.
+ topUids = mStateUidMap[mTopUidState];
+ }
+ }
+ }
+
+ ALOGV("topUidSetChanged: %d", topUidSetChanged);
+
+ if (topUidSetChanged) {
+ auto callback = mUidPolicyCallback.lock();
+ if (callback != nullptr) {
+ callback->onTopUidsChanged(topUids);
+ }
+ }
+}
+
+void TranscodingUidPolicy::updateTopUid_l() {
+ // Update top uid state.
+ mTopUidState = ActivityManager::PROCESS_STATE_UNKNOWN;
+ for (auto stateIt = mStateUidMap.begin(); stateIt != mStateUidMap.end(); stateIt++) {
+ if (stateIt->first != ActivityManager::PROCESS_STATE_UNKNOWN && !stateIt->second.empty()) {
+ mTopUidState = stateIt->first;
+ break;
+ }
+ }
+
+ ALOGV("%s: top uid state is %d", __FUNCTION__, mTopUidState);
+}
+
+int32_t TranscodingUidPolicy::getProcState_l(uid_t uid) {
+ auto it = mUidStateMap.find(uid);
+ if (it != mUidStateMap.end()) {
+ return it->second;
+ }
+ return ActivityManager::PROCESS_STATE_UNKNOWN;
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/include/media/ProcessInfoInterface.h b/media/libmediatranscoding/include/media/ProcessInfoInterface.h
deleted file mode 100644
index ef79266..0000000
--- a/media/libmediatranscoding/include/media/ProcessInfoInterface.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2020 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 ANDROID_MEDIA_PROCESS_INFO_INTERFACE_H
-#define ANDROID_MEDIA_PROCESS_INFO_INTERFACE_H
-
-namespace android {
-
-// Interface for the scheduler to query a process's info.
-class ProcessInfoInterface {
-public:
- // Determines if a process is currently running as top process.
- // TODO(chz): this should probably be replaced by a query that determines
- // which pid has the highest priority among a given set of pids. For now,
- // we assume that there is a way to determine based on a pid number whether
- // that pid is on "top", but this may not be possible in some cases, for
- // example, the client process with highest priority is actually a foreground
- // service (serving the top-app), but technically is not "top".
- virtual bool isProcessOnTop(pid_t pid) = 0;
-
-protected:
- virtual ~ProcessInfoInterface() = default;
-};
-
-// Interface for notifying the scheduler of a change in a process's state or
-// transcoding resource availability.
-class ProcessInfoCallbackInterface {
-public:
- // Called when a process with pid is brought to top.
- // TODO(chz): this should probably be replace by a callback when the pid
- // that was previously identified being the highest priority as in
- // ProcessInfoInterface::isProcessOnTop() has changed in priority.
- virtual void onTopProcessChanged(pid_t pid) = 0;
-
- // Called when resources become available for transcoding use. The scheduler
- // may use this as a signal to attempt restart transcoding activity that
- // were previously paused due to temporary resource loss.
- virtual void onResourceAvailable() = 0;
-
-protected:
- virtual ~ProcessInfoCallbackInterface() = default;
-};
-
-} // namespace android
-#endif // ANDROID_MEDIA_PROCESS_INFO_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/SchedulerClientInterface.h b/media/libmediatranscoding/include/media/SchedulerClientInterface.h
index b4cf463..6ccf117 100644
--- a/media/libmediatranscoding/include/media/SchedulerClientInterface.h
+++ b/media/libmediatranscoding/include/media/SchedulerClientInterface.h
@@ -31,7 +31,7 @@
// the status of a job.
class SchedulerClientInterface {
public:
- virtual bool submit(ClientIdType clientId, int32_t jobId, pid_t pid,
+ virtual bool submit(ClientIdType clientId, int32_t jobId, uid_t uid,
const TranscodingRequestParcel& request,
const std::weak_ptr<ITranscodingClientCallback>& clientCallback) = 0;
diff --git a/media/libmediatranscoding/include/media/TranscoderInterface.h b/media/libmediatranscoding/include/media/TranscoderInterface.h
index d74135a..a2afa00 100644
--- a/media/libmediatranscoding/include/media/TranscoderInterface.h
+++ b/media/libmediatranscoding/include/media/TranscoderInterface.h
@@ -43,6 +43,7 @@
// TODO(chz): determine what parameters are needed here.
virtual void onFinish(int64_t clientId, int32_t jobId) = 0;
virtual void onError(int64_t clientId, int32_t jobId, TranscodingErrorCode err) = 0;
+ virtual void onProgressUpdate(int64_t clientId, int32_t jobId, int32_t progress) = 0;
// Called when transcoding becomes temporarily inaccessible due to loss of resource.
// If there is any job currently running, it will be paused. When resource contention
diff --git a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
index f4d98ba..0838977 100644
--- a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
+++ b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
@@ -18,10 +18,10 @@
#define ANDROID_MEDIA_TRANSCODING_JOB_SCHEDULER_H
#include <aidl/android/media/TranscodingJobPriority.h>
-#include <media/ProcessInfoInterface.h>
#include <media/SchedulerClientInterface.h>
#include <media/TranscoderInterface.h>
#include <media/TranscodingRequest.h>
+#include <media/UidPolicyInterface.h>
#include <utils/String8.h>
#include <list>
@@ -32,14 +32,14 @@
using ::aidl::android::media::TranscodingJobPriority;
using ::aidl::android::media::TranscodingResultParcel;
-class TranscodingJobScheduler : public ProcessInfoCallbackInterface,
+class TranscodingJobScheduler : public UidPolicyCallbackInterface,
public SchedulerClientInterface,
public TranscoderCallbackInterface {
public:
virtual ~TranscodingJobScheduler();
// SchedulerClientInterface
- bool submit(ClientIdType clientId, int32_t jobId, pid_t pid,
+ bool submit(ClientIdType clientId, int32_t jobId, uid_t uid,
const TranscodingRequestParcel& request,
const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override;
bool cancel(ClientIdType clientId, int32_t jobId) override;
@@ -49,13 +49,14 @@
// TranscoderCallbackInterface
void onFinish(ClientIdType clientId, int32_t jobId) override;
void onError(int64_t clientId, int32_t jobId, TranscodingErrorCode err) override;
+ void onProgressUpdate(int64_t clientId, int32_t jobId, int32_t progress) override;
void onResourceLost() override;
// ~TranscoderCallbackInterface
- // ProcessInfoCallbackInterface
- void onTopProcessChanged(int32_t pid) override;
+ // UidPolicyCallbackInterface
+ void onTopUidsChanged(const std::unordered_set<uid_t>& uids) override;
void onResourceAvailable() override;
- // ~ProcessInfoCallbackInterface
+ // ~UidPolicyCallbackInterface
private:
friend class MediaTranscodingService;
@@ -66,7 +67,7 @@
struct Job {
JobKeyType key;
- pid_t pid;
+ uid_t uid;
enum JobState {
NOT_STARTED,
RUNNING,
@@ -82,27 +83,28 @@
std::map<JobKeyType, Job> mJobMap;
- // Pid->JobQueue map (pid == -1: offline queue)
- std::map<pid_t, JobQueueType> mJobQueues;
+ // uid->JobQueue map (uid == -1: offline queue)
+ std::map<uid_t, JobQueueType> mJobQueues;
- // Pids, with the head being the most-recently-top app, 2nd item is the
+ // uids, with the head being the most-recently-top app, 2nd item is the
// previous top app, etc.
- std::list<pid_t> mPidSortedList;
- std::list<pid_t>::iterator mOfflinePidIterator;
+ std::list<uid_t> mUidSortedList;
+ std::list<uid_t>::iterator mOfflineUidIterator;
std::shared_ptr<TranscoderInterface> mTranscoder;
- std::shared_ptr<ProcessInfoInterface> mProcInfo;
+ std::shared_ptr<UidPolicyInterface> mUidPolicy;
Job* mCurrentJob;
bool mResourceLost;
// Only allow MediaTranscodingService and unit tests to instantiate.
TranscodingJobScheduler(const std::shared_ptr<TranscoderInterface>& transcoder,
- const std::shared_ptr<ProcessInfoInterface>& procInfo);
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy);
Job* getTopJob_l();
void updateCurrentJob_l();
void removeJob_l(const JobKeyType& jobKey);
+ void moveUidsToTop_l(const std::unordered_set<uid_t>& uids, bool preserveTopUid);
// Internal state verifier (debug only)
void validateState_l();
diff --git a/media/libmediatranscoding/include/media/TranscodingUidPolicy.h b/media/libmediatranscoding/include/media/TranscodingUidPolicy.h
new file mode 100644
index 0000000..27dadd2
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingUidPolicy.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 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 ANDROID_MEDIA_TRANSCODING_UID_POLICY_H
+#define ANDROID_MEDIA_TRANSCODING_UID_POLICY_H
+
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <media/UidPolicyInterface.h>
+#include <sys/types.h>
+#include <utils/Condition.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <map>
+#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android {
+
+class ActivityManager;
+// Observer for UID lifecycle and provide information about the uid's app
+// priority used by the job scheduler.
+class TranscodingUidPolicy : public UidPolicyInterface {
+public:
+ explicit TranscodingUidPolicy();
+ ~TranscodingUidPolicy();
+
+ // UidPolicyInterface
+ bool isUidOnTop(uid_t uid) override;
+ void registerMonitorUid(uid_t uid) override;
+ void unregisterMonitorUid(uid_t uid) override;
+ std::unordered_set<uid_t> getTopUids() const override;
+ void setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) override;
+
+private:
+ void onUidStateChanged(uid_t uid, int32_t procState);
+ void setUidObserverRegistered(bool registerd);
+ void registerSelf();
+ void unregisterSelf();
+ int32_t getProcState_l(uid_t uid) NO_THREAD_SAFETY_ANALYSIS;
+ void updateTopUid_l() NO_THREAD_SAFETY_ANALYSIS;
+
+ struct UidObserver;
+ mutable Mutex mUidLock;
+ std::shared_ptr<ActivityManager> mAm;
+ sp<UidObserver> mUidObserver;
+ bool mRegistered GUARDED_BY(mUidLock);
+ int32_t mTopUidState GUARDED_BY(mUidLock);
+ std::unordered_map<uid_t, int32_t> mUidStateMap GUARDED_BY(mUidLock);
+ std::map<int32_t, std::unordered_set<uid_t>> mStateUidMap GUARDED_BY(mUidLock);
+ std::weak_ptr<UidPolicyCallbackInterface> mUidPolicyCallback;
+}; // class TranscodingUidPolicy
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODING_SERVICE_H
diff --git a/media/libmediatranscoding/include/media/UidPolicyInterface.h b/media/libmediatranscoding/include/media/UidPolicyInterface.h
new file mode 100644
index 0000000..dc28027
--- /dev/null
+++ b/media/libmediatranscoding/include/media/UidPolicyInterface.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 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 ANDROID_MEDIA_UID_POLICY_INTERFACE_H
+#define ANDROID_MEDIA_UID_POLICY_INTERFACE_H
+
+#include <unordered_set>
+
+namespace android {
+
+class UidPolicyCallbackInterface;
+
+// Interface for the scheduler to query a uid's info.
+class UidPolicyInterface {
+public:
+ // Instruct the uid policy to start monitoring a uid.
+ virtual void registerMonitorUid(uid_t uid) = 0;
+ // Instruct the uid policy to stop monitoring a uid.
+ virtual void unregisterMonitorUid(uid_t uid) = 0;
+ // Whether a uid is among the set of uids that's currently top priority.
+ virtual bool isUidOnTop(uid_t uid) = 0;
+ // Retrieves the set of uids that's currently top priority.
+ virtual std::unordered_set<uid_t> getTopUids() const = 0;
+ // Set the associated callback interface to send the events when uid states change.
+ virtual void setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) = 0;
+
+protected:
+ virtual ~UidPolicyInterface() = default;
+};
+
+// Interface for notifying the scheduler of a change in uid states or
+// transcoding resource availability.
+class UidPolicyCallbackInterface {
+public:
+ // Called when the set of uids that's top priority among the uids of interest
+ // has changed. The receiver of this callback should adjust accordingly.
+ virtual void onTopUidsChanged(const std::unordered_set<uid_t>& uids) = 0;
+
+ // Called when resources become available for transcoding use. The scheduler
+ // may use this as a signal to attempt restart transcoding activity that
+ // were previously paused due to temporary resource loss.
+ virtual void onResourceAvailable() = 0;
+
+protected:
+ virtual ~UidPolicyCallbackInterface() = default;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_UID_POLICY_INTERFACE_H
diff --git a/media/libmediatranscoding/tests/Android.bp b/media/libmediatranscoding/tests/Android.bp
index 904cf9b..b54022a 100644
--- a/media/libmediatranscoding/tests/Android.bp
+++ b/media/libmediatranscoding/tests/Android.bp
@@ -37,6 +37,9 @@
srcs: ["TranscodingClientManager_tests.cpp"],
}
+//
+// TranscodingJobScheduler unit test
+//
cc_test {
name: "TranscodingJobScheduler_tests",
defaults: ["libmediatranscoding_test_defaults"],
@@ -52,4 +55,4 @@
defaults: ["libmediatranscoding_test_defaults"],
srcs: ["AdjustableMaxPriorityQueue_tests.cpp"],
-}
\ No newline at end of file
+}
diff --git a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
index acc3082..7e5ae61 100644
--- a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
@@ -120,7 +120,7 @@
virtual ~TestScheduler() { ALOGI("TestScheduler Destroyed"); }
- bool submit(int64_t clientId, int32_t jobId, pid_t /*pid*/,
+ bool submit(int64_t clientId, int32_t jobId, uid_t /*uid*/,
const TranscodingRequestParcel& request,
const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override {
JobKeyType jobKey = std::make_pair(clientId, jobId);
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
index d062d93..95edf1d 100644
--- a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -31,6 +31,8 @@
#include <media/TranscodingJobScheduler.h>
#include <utils/Log.h>
+#include <unordered_set>
+
namespace android {
using Status = ::ndk::ScopedAStatus;
@@ -40,17 +42,46 @@
constexpr int64_t kClientId = 1000;
constexpr int32_t kClientJobId = 0;
-constexpr pid_t kClientPid = 5000;
-constexpr pid_t kInvalidPid = -1;
+constexpr uid_t kClientUid = 5000;
+constexpr uid_t kInvalidUid = (uid_t)-1;
#define CLIENT(n) (kClientId + (n))
#define JOB(n) (kClientJobId + (n))
-#define PID(n) (kClientPid + (n))
+#define UID(n) (kClientUid + (n))
-class TestCallback : public TranscoderInterface, public ProcessInfoInterface {
+class TestUidPolicy : public UidPolicyInterface {
public:
- TestCallback() : mTopPid(-1), mLastError(TranscodingErrorCode::kUnknown) {}
- virtual ~TestCallback() {}
+ TestUidPolicy() = default;
+ virtual ~TestUidPolicy() = default;
+
+ // UidPolicyInterface
+ void registerMonitorUid(uid_t /*uid*/) override {}
+ void unregisterMonitorUid(uid_t /*uid*/) override {}
+ bool isUidOnTop(uid_t uid) override { return mTopUids.count(uid) > 0; }
+ std::unordered_set<uid_t> getTopUids() const override { return mTopUids; }
+ void setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) override {
+ mUidPolicyCallback = cb;
+ }
+ void setTop(uid_t uid) {
+ std::unordered_set<uid_t> uids = {uid};
+ setTop(uids);
+ }
+ void setTop(const std::unordered_set<uid_t>& uids) {
+ mTopUids = uids;
+ auto uidPolicyCb = mUidPolicyCallback.lock();
+ if (uidPolicyCb != nullptr) {
+ uidPolicyCb->onTopUidsChanged(mTopUids);
+ }
+ }
+
+ std::unordered_set<uid_t> mTopUids;
+ std::weak_ptr<UidPolicyCallbackInterface> mUidPolicyCallback;
+};
+
+class TestTranscoder : public TranscoderInterface {
+public:
+ TestTranscoder() : mLastError(TranscodingErrorCode::kUnknown) {}
+ virtual ~TestTranscoder() {}
// TranscoderInterface
void start(int64_t clientId, int32_t jobId) override {
@@ -63,9 +94,6 @@
mEventQueue.push_back(Resume(clientId, jobId));
}
- // ProcessInfoInterface
- bool isProcessOnTop(pid_t pid) override { return pid == mTopPid; }
-
void onFinished(int64_t clientId, int32_t jobId) {
mEventQueue.push_back(Finished(clientId, jobId));
}
@@ -75,8 +103,6 @@
mEventQueue.push_back(Failed(clientId, jobId));
}
- void setTop(pid_t pid) { mTopPid = pid; }
-
TranscodingErrorCode getLastError() {
TranscodingErrorCode result = mLastError;
mLastError = TranscodingErrorCode::kUnknown;
@@ -115,16 +141,16 @@
private:
Event mPoppedEvent;
std::list<Event> mEventQueue;
- pid_t mTopPid;
TranscodingErrorCode mLastError;
};
-bool operator==(const TestCallback::Event& lhs, const TestCallback::Event& rhs) {
+bool operator==(const TestTranscoder::Event& lhs, const TestTranscoder::Event& rhs) {
return lhs.type == rhs.type && lhs.clientId == rhs.clientId && lhs.jobId == rhs.jobId;
}
struct TestClientCallback : public BnTranscodingClientCallback {
- TestClientCallback(TestCallback* owner, int64_t clientId) : mOwner(owner), mClientId(clientId) {
+ TestClientCallback(TestTranscoder* owner, int64_t clientId)
+ : mOwner(owner), mClientId(clientId) {
ALOGD("TestClient Created");
}
@@ -152,7 +178,7 @@
virtual ~TestClientCallback() { ALOGI("TestClient destroyed"); };
private:
- TestCallback* mOwner;
+ TestTranscoder* mOwner;
int64_t mClientId;
TestClientCallback(const TestClientCallback&) = delete;
TestClientCallback& operator=(const TestClientCallback&) = delete;
@@ -164,27 +190,30 @@
void SetUp() override {
ALOGI("TranscodingJobSchedulerTest set up");
- mCallback.reset(new TestCallback());
- mScheduler.reset(new TranscodingJobScheduler(mCallback, mCallback));
+ mTranscoder.reset(new TestTranscoder());
+ mUidPolicy.reset(new TestUidPolicy());
+ mScheduler.reset(new TranscodingJobScheduler(mTranscoder, mUidPolicy));
+ mUidPolicy->setCallback(mScheduler);
// Set priority only, ignore other fields for now.
mOfflineRequest.priority = TranscodingJobPriority::kUnspecified;
mRealtimeRequest.priority = TranscodingJobPriority::kHigh;
mClientCallback0 =
- ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(0));
+ ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(0));
mClientCallback1 =
- ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(1));
+ ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(1));
mClientCallback2 =
- ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(2));
+ ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(2));
mClientCallback3 =
- ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(3));
+ ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(3));
}
void TearDown() override { ALOGI("TranscodingJobSchedulerTest tear down"); }
~TranscodingJobSchedulerTest() { ALOGD("TranscodingJobSchedulerTest destroyed"); }
- std::shared_ptr<TestCallback> mCallback;
+ std::shared_ptr<TestTranscoder> mTranscoder;
+ std::shared_ptr<TestUidPolicy> mUidPolicy;
std::shared_ptr<TranscodingJobScheduler> mScheduler;
TranscodingRequestParcel mOfflineRequest;
TranscodingRequestParcel mRealtimeRequest;
@@ -197,61 +226,59 @@
TEST_F(TranscodingJobSchedulerTest, TestSubmitJob) {
ALOGD("TestSubmitJob");
- // Start with PID(1) on top.
- mCallback->setTop(PID(1));
+ // Start with UID(1) on top.
+ mUidPolicy->setTop(UID(1));
- // Submit offline job to CLIENT(0) in PID(0).
+ // Submit offline job to CLIENT(0) in UID(0).
// Should start immediately (because this is the only job).
- mScheduler->submit(CLIENT(0), JOB(0), PID(0), mOfflineRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), 0));
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), 0));
// Submit real-time job to CLIENT(0).
- // Should pause offline job and start new job, even if PID(0) is not on top.
- mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+ // Should pause offline job and start new job, even if UID(0) is not on top.
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(1)));
// Submit real-time job to CLIENT(0), should be queued after the previous job.
- mScheduler->submit(CLIENT(0), JOB(2), PID(0), mRealtimeRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- // Submit real-time job to CLIENT(1) in same pid, should be queued after the previous job.
- mScheduler->submit(CLIENT(1), JOB(0), PID(0), mRealtimeRequest, mClientCallback1);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ // Submit real-time job to CLIENT(1) in same uid, should be queued after the previous job.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- // Submit real-time job to CLIENT(2) in PID(1).
- // Should pause previous job and start new job, because PID(1) is top.
- mCallback->setTop(PID(1));
- mScheduler->submit(CLIENT(2), JOB(0), PID(1), mRealtimeRequest, mClientCallback2);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+ // Submit real-time job to CLIENT(2) in UID(1).
+ // Should pause previous job and start new job, because UID(1) is (has been) top.
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
// Submit offline job, shouldn't generate any event.
- mScheduler->submit(CLIENT(2), JOB(1), PID(1), mOfflineRequest, mClientCallback2);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ mScheduler->submit(CLIENT(2), JOB(1), UID(1), mOfflineRequest, mClientCallback2);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- mCallback->setTop(PID(0));
- // Submit real-time job to CLIENT(1) in PID(0).
- // Should pause current job, and resume last job in PID(0).
- mScheduler->submit(CLIENT(1), JOB(1), PID(0), mRealtimeRequest, mClientCallback1);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(2), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(1)));
+ // Bring UID(0) to top.
+ mUidPolicy->setTop(UID(0));
+ // Should pause current job, and resume last job in UID(0).
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(1)));
}
TEST_F(TranscodingJobSchedulerTest, TestCancelJob) {
ALOGD("TestCancelJob");
// Submit real-time job JOB(0), should start immediately.
- mScheduler->submit(CLIENT(0), JOB(0), PID(0), mRealtimeRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
// Submit real-time job JOB(1), should not start.
- mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
// Submit offline job JOB(2), should not start.
- mScheduler->submit(CLIENT(0), JOB(2), PID(0), mOfflineRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
// Cancel queued real-time job.
// Cancel real-time job JOB(1), should be cancelled.
@@ -262,225 +289,284 @@
EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(2)));
// Submit offline job JOB(3), shouldn't cause any event.
- mScheduler->submit(CLIENT(0), JOB(3), PID(0), mOfflineRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ mScheduler->submit(CLIENT(0), JOB(3), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
// Cancel running real-time job JOB(0).
// - Should be paused first then cancelled.
// - Should also start offline job JOB(2) because real-time queue is empty.
EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(3)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(3)));
}
TEST_F(TranscodingJobSchedulerTest, TestFinishJob) {
ALOGD("TestFinishJob");
- // Fail without any jobs submitted, should be ignored.
+ // Start with unspecified top UID.
+ // Finish without any jobs submitted, should be ignored.
mScheduler->onFinish(CLIENT(0), JOB(0));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
// Submit offline job JOB(0), should start immediately.
- mScheduler->submit(CLIENT(0), JOB(0), PID(0), mOfflineRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
// Submit real-time job JOB(1), should pause offline job and start immediately.
- mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(1)));
// Submit real-time job JOB(2), should not start.
- mScheduler->submit(CLIENT(0), JOB(2), PID(0), mRealtimeRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- // Fail when the job never started, should be ignored.
+ // Finish when the job never started, should be ignored.
mScheduler->onFinish(CLIENT(0), JOB(2));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- // PID(1) moves to top.
- mCallback->setTop(PID(1));
- // Submit real-time job to CLIENT(1) in PID(1), should pause previous job and start new job.
- mScheduler->submit(CLIENT(1), JOB(0), PID(1), mRealtimeRequest, mClientCallback1);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+ // UID(1) moves to top.
+ mUidPolicy->setTop(UID(1));
+ // Submit real-time job to CLIENT(1) in UID(1), should pause previous job and start new job.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(1), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
- // Simulate Fail that arrived late, after pause issued by scheduler.
+ // Simulate Finish that arrived late, after pause issued by scheduler.
// Should still be propagated to client, but shouldn't trigger any new start.
mScheduler->onFinish(CLIENT(0), JOB(1));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(1)));
- // Fail running real-time job, should start next real-time job in queue.
+ // Finish running real-time job, should start next real-time job in queue.
mScheduler->onFinish(CLIENT(1), JOB(0));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(1), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(2)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(1), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(2)));
- // Fail running real-time job, should resume next job (offline job) in queue.
+ // Finish running real-time job, should resume next job (offline job) in queue.
mScheduler->onFinish(CLIENT(0), JOB(2));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(2)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(2)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
- // Fail running offline job.
+ // Finish running offline job.
mScheduler->onFinish(CLIENT(0), JOB(0));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(0)));
- // Duplicate fail for last job, should be ignored.
+ // Duplicate finish for last job, should be ignored.
mScheduler->onFinish(CLIENT(0), JOB(0));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
}
TEST_F(TranscodingJobSchedulerTest, TestFailJob) {
ALOGD("TestFailJob");
+ // Start with unspecified top UID.
// Fail without any jobs submitted, should be ignored.
mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
// Submit offline job JOB(0), should start immediately.
- mScheduler->submit(CLIENT(0), JOB(0), PID(0), mOfflineRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
// Submit real-time job JOB(1), should pause offline job and start immediately.
- mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+ mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(1)));
// Submit real-time job JOB(2), should not start.
- mScheduler->submit(CLIENT(0), JOB(2), PID(0), mRealtimeRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
// Fail when the job never started, should be ignored.
mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- // PID(1) moves to top.
- mCallback->setTop(PID(1));
- // Submit real-time job to CLIENT(1) in PID(1), should pause previous job and start new job.
- mScheduler->submit(CLIENT(1), JOB(0), PID(1), mRealtimeRequest, mClientCallback1);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+ // UID(1) moves to top.
+ mUidPolicy->setTop(UID(1));
+ // Submit real-time job to CLIENT(1) in UID(1), should pause previous job and start new job.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(1), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
// Simulate Fail that arrived late, after pause issued by scheduler.
// Should still be propagated to client, but shouldn't trigger any new start.
mScheduler->onError(CLIENT(0), JOB(1), TranscodingErrorCode::kUnknown);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(0), JOB(1)));
// Fail running real-time job, should start next real-time job in queue.
mScheduler->onError(CLIENT(1), JOB(0), TranscodingErrorCode::kUnknown);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(1), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(2)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(1), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(2)));
// Fail running real-time job, should resume next job (offline job) in queue.
mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(2)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(0), JOB(2)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
// Fail running offline job, and test error code propagation.
mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kInvalidBitstream);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(0)));
- EXPECT_EQ(mCallback->getLastError(), TranscodingErrorCode::kInvalidBitstream);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->getLastError(), TranscodingErrorCode::kInvalidBitstream);
// Duplicate fail for last job, should be ignored.
mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
}
-TEST_F(TranscodingJobSchedulerTest, TestTopProcessChanged) {
- ALOGD("TestTopProcessChanged");
+TEST_F(TranscodingJobSchedulerTest, TestTopUidChanged) {
+ ALOGD("TestTopUidChanged");
+ // Start with unspecified top UID.
// Submit real-time job to CLIENT(0), job should start immediately.
- mScheduler->submit(CLIENT(0), JOB(0), PID(0), mRealtimeRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
// Submit offline job to CLIENT(0), should not start.
- mScheduler->submit(CLIENT(1), JOB(0), PID(0), mOfflineRequest, mClientCallback1);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- // Move PID(1) to top.
- mCallback->setTop(PID(1));
- // Submit real-time job to CLIENT(2) in different pid PID(1).
+ // Move UID(1) to top.
+ mUidPolicy->setTop(UID(1));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Submit real-time job to CLIENT(2) in different uid UID(1).
// Should pause previous job and start new job.
- mScheduler->submit(CLIENT(2), JOB(0), PID(1), mRealtimeRequest, mClientCallback2);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
- // Bring PID(0) back to top.
- mCallback->setTop(PID(0));
- mScheduler->onTopProcessChanged(PID(0));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(2), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+ // Bring UID(0) back to top.
+ mUidPolicy->setTop(UID(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
- // Bring invalid process to top.
- mScheduler->onTopProcessChanged(kInvalidPid);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ // Bring invalid uid to top.
+ mUidPolicy->setTop(kInvalidUid);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
// Finish job, next real-time job should resume.
mScheduler->onFinish(CLIENT(0), JOB(0));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), JOB(0)));
// Finish job, offline job should start.
mScheduler->onFinish(CLIENT(2), JOB(0));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(2), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestTopUidSetChanged) {
+ ALOGD("TestTopUidChanged_MultipleUids");
+
+ // Start with unspecified top UID.
+ // Submit real-time job to CLIENT(0), job should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
+
+ // Submit offline job to CLIENT(0), should not start.
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Set UID(0), UID(1) to top set.
+ // UID(0) should continue to run.
+ mUidPolicy->setTop({UID(0), UID(1)});
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Submit real-time job to CLIENT(2) in different uid UID(1).
+ // UID(0) should pause and UID(1) should start.
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
+
+ // Remove UID(0) from top set, and only leave UID(1) in the set.
+ // UID(1) should continue to run.
+ mUidPolicy->setTop(UID(1));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Set UID(0), UID(2) to top set.
+ // UID(1) should continue to run.
+ mUidPolicy->setTop({UID(1), UID(2)});
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Bring UID(0) back to top.
+ mUidPolicy->setTop(UID(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
+
+ // Bring invalid uid to top.
+ mUidPolicy->setTop(kInvalidUid);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+ // Finish job, next real-time job from UID(1) should resume, even if UID(1) no longer top.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), JOB(0)));
+
+ // Finish job, offline job should start.
+ mScheduler->onFinish(CLIENT(2), JOB(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
}
TEST_F(TranscodingJobSchedulerTest, TestResourceLost) {
ALOGD("TestResourceLost");
+ // Start with unspecified top UID.
// Submit real-time job to CLIENT(0), job should start immediately.
- mScheduler->submit(CLIENT(0), JOB(0), PID(0), mRealtimeRequest, mClientCallback0);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+ mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
// Submit offline job to CLIENT(0), should not start.
- mScheduler->submit(CLIENT(1), JOB(0), PID(0), mOfflineRequest, mClientCallback1);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- // Move PID(1) to top.
- mCallback->setTop(PID(1));
+ // Move UID(1) to top.
+ mUidPolicy->setTop(UID(1));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- // Submit real-time job to CLIENT(2) in different pid PID(1).
+ // Submit real-time job to CLIENT(2) in different uid UID(1).
// Should pause previous job and start new job.
- mScheduler->submit(CLIENT(2), JOB(0), PID(1), mRealtimeRequest, mClientCallback2);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+ mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
// Test 1: No queue change during resource loss.
// Signal resource lost.
mScheduler->onResourceLost();
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
// Signal resource available, CLIENT(2) should resume.
mScheduler->onResourceAvailable();
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), JOB(0)));
// Test 2: Change of queue order during resource loss.
// Signal resource lost.
mScheduler->onResourceLost();
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- // Move PID(0) back to top, should have no resume due to no resource.
- mScheduler->onTopProcessChanged(PID(0));
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ // Move UID(0) back to top, should have no resume due to no resource.
+ mUidPolicy->setTop(UID(0));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
// Signal resource available, CLIENT(0) should resume.
mScheduler->onResourceAvailable();
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
// Test 3: Adding new queue during resource loss.
// Signal resource lost.
mScheduler->onResourceLost();
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
- // Move PID(2) to top.
- mCallback->setTop(PID(2));
+ // Move UID(2) to top.
+ mUidPolicy->setTop(UID(2));
- // Submit real-time job to CLIENT(3) in PID(2), job shouldn't start due to no resource.
- mScheduler->submit(CLIENT(3), JOB(0), PID(2), mRealtimeRequest, mClientCallback3);
- EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+ // Submit real-time job to CLIENT(3) in UID(2), job shouldn't start due to no resource.
+ mScheduler->submit(CLIENT(3), JOB(0), UID(2), mRealtimeRequest, mClientCallback3);
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
// Signal resource available, CLIENT(3)'s job should start.
mScheduler->onResourceAvailable();
- EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(3), JOB(0)));
+ EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(3), JOB(0)));
}
} // namespace android
diff --git a/media/libmediatranscoding/transcoder/Android.bp b/media/libmediatranscoding/transcoder/Android.bp
index e352245..44f7959 100644
--- a/media/libmediatranscoding/transcoder/Android.bp
+++ b/media/libmediatranscoding/transcoder/Android.bp
@@ -20,12 +20,15 @@
srcs: [
"MediaSampleQueue.cpp",
"MediaSampleReaderNDK.cpp",
+ "MediaTrackTranscoder.cpp",
+ "VideoTrackTranscoder.cpp",
],
shared_libs: [
"libbase",
"libcutils",
"libmediandk",
+ "libnativewindow",
"libutils",
],
diff --git a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
index d0f117d..a0096c7 100644
--- a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
+++ b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
@@ -25,6 +25,10 @@
namespace android {
+// Check that the extractor sample flags have the expected NDK meaning.
+static_assert(SAMPLE_FLAG_SYNC_SAMPLE == AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC,
+ "Sample flag mismatch: SYNC_SAMPLE");
+
// static
std::shared_ptr<MediaSampleReader> MediaSampleReaderNDK::createFromFd(int fd, size_t offset,
size_t size) {
diff --git a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
new file mode 100644
index 0000000..1673b5b
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 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 "MediaTrackTranscoder"
+
+#include <android-base/logging.h>
+#include <media/MediaTrackTranscoder.h>
+
+namespace android {
+
+media_status_t MediaTrackTranscoder::configure(
+ const std::shared_ptr<MediaSampleReader>& mediaSampleReader, int trackIndex,
+ const std::shared_ptr<AMediaFormat>& destinationFormat) {
+ std::scoped_lock lock{mStateMutex};
+
+ if (mState != UNINITIALIZED) {
+ LOG(ERROR) << "Configure can only be called once";
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ if (mediaSampleReader == nullptr) {
+ LOG(ERROR) << "MediaSampleReader is null";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+ if (trackIndex < 0 || trackIndex >= mediaSampleReader->getTrackCount()) {
+ LOG(ERROR) << "TrackIndex is invalid " << trackIndex;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ mMediaSampleReader = mediaSampleReader;
+ mTrackIndex = trackIndex;
+
+ mSourceFormat =
+ std::shared_ptr<AMediaFormat>(mMediaSampleReader->getTrackFormat(mTrackIndex),
+ std::bind(AMediaFormat_delete, std::placeholders::_1));
+ if (mSourceFormat == nullptr) {
+ LOG(ERROR) << "Unable to get format for track #" << mTrackIndex;
+ return AMEDIA_ERROR_MALFORMED;
+ }
+
+ media_status_t status = configureDestinationFormat(destinationFormat);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "configure failed with error " << status;
+ return status;
+ }
+
+ mState = CONFIGURED;
+ return AMEDIA_OK;
+}
+
+bool MediaTrackTranscoder::start() {
+ std::scoped_lock lock{mStateMutex};
+
+ if (mState != CONFIGURED) {
+ LOG(ERROR) << "TrackTranscoder must be configured before started";
+ return false;
+ }
+
+ mTranscodingThread = std::thread([this] {
+ media_status_t status = runTranscodeLoop();
+
+ // Notify the client.
+ if (auto callbacks = mTranscoderCallback.lock()) {
+ if (status != AMEDIA_OK) {
+ callbacks->onTrackError(this, status);
+ } else {
+ callbacks->onTrackFinished(this);
+ }
+ }
+ });
+
+ mState = STARTED;
+ return true;
+}
+
+bool MediaTrackTranscoder::stop() {
+ std::scoped_lock lock{mStateMutex};
+
+ if (mState == STARTED) {
+ abortTranscodeLoop();
+ mTranscodingThread.join();
+ mState = STOPPED;
+ return true;
+ }
+
+ LOG(ERROR) << "TrackTranscoder must be started before stopped";
+ return false;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
new file mode 100644
index 0000000..9cd36cf
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2020 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 "VideoTrackTranscoder"
+
+#include <android-base/logging.h>
+#include <media/VideoTrackTranscoder.h>
+
+namespace android {
+
+// Check that the codec sample flags have the expected NDK meaning.
+static_assert(SAMPLE_FLAG_CODEC_CONFIG == AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG,
+ "Sample flag mismatch: CODEC_CONFIG");
+static_assert(SAMPLE_FLAG_END_OF_STREAM == AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM,
+ "Sample flag mismatch: END_OF_STREAM");
+static_assert(SAMPLE_FLAG_PARTIAL_FRAME == AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME,
+ "Sample flag mismatch: PARTIAL_FRAME");
+
+template <typename T>
+void VideoTrackTranscoder::BlockingQueue<T>::push(T const& value, bool front) {
+ {
+ std::unique_lock<std::mutex> lock(mMutex);
+ if (front) {
+ mQueue.push_front(value);
+ } else {
+ mQueue.push_back(value);
+ }
+ }
+ mCondition.notify_one();
+}
+
+template <typename T>
+T VideoTrackTranscoder::BlockingQueue<T>::pop() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (mQueue.empty()) {
+ mCondition.wait(lock);
+ }
+ T value = mQueue.front();
+ mQueue.pop_front();
+ return value;
+}
+
+// Dispatch responses to codec callbacks onto the message queue.
+struct AsyncCodecCallbackDispatch {
+ static void onAsyncInputAvailable(AMediaCodec* codec, void* userdata, int32_t index) {
+ VideoTrackTranscoder* transcoder = static_cast<VideoTrackTranscoder*>(userdata);
+ if (codec == transcoder->mDecoder) {
+ transcoder->mCodecMessageQueue.push(
+ [transcoder, index] { transcoder->enqueueInputSample(index); });
+ }
+ }
+
+ static void onAsyncOutputAvailable(AMediaCodec* codec, void* userdata, int32_t index,
+ AMediaCodecBufferInfo* bufferInfoPtr) {
+ VideoTrackTranscoder* transcoder = static_cast<VideoTrackTranscoder*>(userdata);
+ AMediaCodecBufferInfo bufferInfo = *bufferInfoPtr;
+ transcoder->mCodecMessageQueue.push([transcoder, index, codec, bufferInfo] {
+ if (codec == transcoder->mDecoder) {
+ transcoder->transferBuffer(index, bufferInfo);
+ } else if (codec == transcoder->mEncoder) {
+ transcoder->dequeueOutputSample(index, bufferInfo);
+ }
+ });
+ }
+
+ static void onAsyncFormatChanged(AMediaCodec* codec, void* userdata, AMediaFormat* format) {
+ VideoTrackTranscoder* transcoder = static_cast<VideoTrackTranscoder*>(userdata);
+ const char* kCodecName = (codec == transcoder->mDecoder ? "Decoder" : "Encoder");
+ LOG(DEBUG) << kCodecName << " format changed: " << AMediaFormat_toString(format);
+ }
+
+ static void onAsyncError(AMediaCodec* codec, void* userdata, media_status_t error,
+ int32_t actionCode, const char* detail) {
+ LOG(ERROR) << "Error from codec " << codec << ", userdata " << userdata << ", error "
+ << error << ", action " << actionCode << ", detail " << detail;
+ VideoTrackTranscoder* transcoder = static_cast<VideoTrackTranscoder*>(userdata);
+ transcoder->mCodecMessageQueue.push(
+ [transcoder, error] {
+ transcoder->mStatus = error;
+ transcoder->mStopRequested = true;
+ },
+ true);
+ }
+};
+
+VideoTrackTranscoder::~VideoTrackTranscoder() {
+ if (mDecoder != nullptr) {
+ AMediaCodec_delete(mDecoder);
+ }
+
+ if (mEncoder != nullptr) {
+ AMediaCodec_delete(mEncoder);
+ }
+
+ if (mSurface != nullptr) {
+ ANativeWindow_release(mSurface);
+ }
+}
+
+// Creates and configures the codecs.
+media_status_t VideoTrackTranscoder::configureDestinationFormat(
+ const std::shared_ptr<AMediaFormat>& destinationFormat) {
+ media_status_t status = AMEDIA_OK;
+
+ if (destinationFormat == nullptr) {
+ LOG(ERROR) << "Destination format is null";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ mDestinationFormat = destinationFormat;
+
+ // Create and configure the encoder.
+ const char* destinationMime = nullptr;
+ bool ok = AMediaFormat_getString(mDestinationFormat.get(), AMEDIAFORMAT_KEY_MIME,
+ &destinationMime);
+ if (!ok) {
+ LOG(ERROR) << "Destination MIME type is required for transcoding.";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ mEncoder = AMediaCodec_createEncoderByType(destinationMime);
+ if (mEncoder == nullptr) {
+ LOG(ERROR) << "Unable to create encoder for type " << destinationMime;
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ status = AMediaCodec_configure(mEncoder, mDestinationFormat.get(), NULL /* surface */,
+ NULL /* crypto */, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to configure video encoder: " << status;
+ return status;
+ }
+
+ status = AMediaCodec_createInputSurface(mEncoder, &mSurface);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to create an encoder input surface: %d" << status;
+ return status;
+ }
+
+ // Create and configure the decoder.
+ const char* sourceMime = nullptr;
+ ok = AMediaFormat_getString(mSourceFormat.get(), AMEDIAFORMAT_KEY_MIME, &sourceMime);
+ if (!ok) {
+ LOG(ERROR) << "Source MIME type is required for transcoding.";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ mDecoder = AMediaCodec_createDecoderByType(sourceMime);
+ if (mDecoder == nullptr) {
+ LOG(ERROR) << "Unable to create decoder for type " << sourceMime;
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ status = AMediaCodec_configure(mDecoder, mSourceFormat.get(), mSurface, NULL /* crypto */,
+ 0 /* flags */);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to configure video decoder: " << status;
+ return status;
+ }
+
+ // Configure codecs to run in async mode.
+ AMediaCodecOnAsyncNotifyCallback asyncCodecCallbacks = {
+ .onAsyncInputAvailable = AsyncCodecCallbackDispatch::onAsyncInputAvailable,
+ .onAsyncOutputAvailable = AsyncCodecCallbackDispatch::onAsyncOutputAvailable,
+ .onAsyncFormatChanged = AsyncCodecCallbackDispatch::onAsyncFormatChanged,
+ .onAsyncError = AsyncCodecCallbackDispatch::onAsyncError};
+
+ status = AMediaCodec_setAsyncNotifyCallback(mDecoder, asyncCodecCallbacks, this);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to set decoder to async mode: " << status;
+ return status;
+ }
+
+ status = AMediaCodec_setAsyncNotifyCallback(mEncoder, asyncCodecCallbacks, this);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to set encoder to async mode: " << status;
+ return status;
+ }
+
+ return AMEDIA_OK;
+}
+
+void VideoTrackTranscoder::enqueueInputSample(int32_t bufferIndex) {
+ media_status_t status = AMEDIA_OK;
+
+ if (mEOSFromSource) {
+ return;
+ }
+
+ status = mMediaSampleReader->getSampleInfoForTrack(mTrackIndex, &mSampleInfo);
+ if (status != AMEDIA_OK && status != AMEDIA_ERROR_END_OF_STREAM) {
+ LOG(ERROR) << "Error getting next sample info: " << status;
+ mStatus = status;
+ return;
+ }
+ const bool endOfStream = (status == AMEDIA_ERROR_END_OF_STREAM);
+
+ if (!endOfStream) {
+ size_t bufferSize = 0;
+ uint8_t* sourceBuffer = AMediaCodec_getInputBuffer(mDecoder, bufferIndex, &bufferSize);
+ if (sourceBuffer == nullptr) {
+ LOG(ERROR) << "Decoder returned a NULL input buffer.";
+ mStatus = AMEDIA_ERROR_UNKNOWN;
+ return;
+ } else if (bufferSize < mSampleInfo.size) {
+ LOG(ERROR) << "Decoder returned an input buffer that is smaller than the sample.";
+ mStatus = AMEDIA_ERROR_UNKNOWN;
+ return;
+ }
+
+ status = mMediaSampleReader->readSampleDataForTrack(mTrackIndex, sourceBuffer,
+ mSampleInfo.size);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to read next sample data. Aborting transcode.";
+ mStatus = status;
+ return;
+ }
+
+ mMediaSampleReader->advanceTrack(mTrackIndex);
+ } else {
+ LOG(DEBUG) << "EOS from source.";
+ mEOSFromSource = true;
+ }
+
+ status = AMediaCodec_queueInputBuffer(mDecoder, bufferIndex, 0, mSampleInfo.size,
+ mSampleInfo.presentationTimeUs, mSampleInfo.flags);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to queue input buffer for decode: " << status;
+ mStatus = status;
+ return;
+ }
+}
+
+void VideoTrackTranscoder::transferBuffer(int32_t bufferIndex, AMediaCodecBufferInfo bufferInfo) {
+ if (bufferIndex >= 0) {
+ bool needsRender = bufferInfo.size > 0;
+ AMediaCodec_releaseOutputBuffer(mDecoder, bufferIndex, needsRender);
+ }
+
+ if (bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+ LOG(DEBUG) << "EOS from decoder.";
+ media_status_t status = AMediaCodec_signalEndOfInputStream(mEncoder);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "SignalEOS on encoder returned error: " << status;
+ mStatus = status;
+ }
+ }
+}
+
+void VideoTrackTranscoder::dequeueOutputSample(int32_t bufferIndex,
+ AMediaCodecBufferInfo bufferInfo) {
+ if (bufferIndex >= 0) {
+ size_t sampleSize = 0;
+ uint8_t* buffer = AMediaCodec_getOutputBuffer(mEncoder, bufferIndex, &sampleSize);
+
+ std::shared_ptr<MediaSample> sample = MediaSample::createWithReleaseCallback(
+ buffer, bufferInfo.offset, bufferIndex,
+ std::bind(&VideoTrackTranscoder::releaseOutputSample, this, std::placeholders::_1));
+ sample->info.size = bufferInfo.size;
+ sample->info.flags = bufferInfo.flags;
+ sample->info.presentationTimeUs = bufferInfo.presentationTimeUs;
+
+ const bool aborted = mOutputQueue.enqueue(sample);
+ if (aborted) {
+ LOG(ERROR) << "Output sample queue was aborted. Stopping transcode.";
+ mStatus = AMEDIA_ERROR_IO; // TODO: Define custom error codes?
+ return;
+ }
+ } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+ AMediaFormat* newFormat = AMediaCodec_getOutputFormat(mEncoder);
+ LOG(DEBUG) << "Encoder output format changed: " << AMediaFormat_toString(newFormat);
+ }
+
+ if (bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+ LOG(DEBUG) << "EOS from encoder.";
+ mEOSFromEncoder = true;
+ }
+}
+
+void VideoTrackTranscoder::releaseOutputSample(MediaSample* sample) {
+ AMediaCodec_releaseOutputBuffer(mEncoder, sample->bufferId, false /* render */);
+}
+
+media_status_t VideoTrackTranscoder::runTranscodeLoop() {
+ media_status_t status = AMEDIA_OK;
+
+ status = AMediaCodec_start(mDecoder);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to start video decoder: " << status;
+ return status;
+ }
+
+ status = AMediaCodec_start(mEncoder);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to start video encoder: " << status;
+ AMediaCodec_stop(mDecoder);
+ return status;
+ }
+
+ // Process codec events until EOS is reached, transcoding is stopped or an error occurs.
+ while (!mStopRequested && !mEOSFromEncoder && mStatus == AMEDIA_OK) {
+ std::function<void()> message = mCodecMessageQueue.pop();
+ message();
+ }
+
+ // Return error if transcoding was stopped before it finished.
+ if (mStopRequested && !mEOSFromEncoder && mStatus == AMEDIA_OK) {
+ mStatus = AMEDIA_ERROR_UNKNOWN; // TODO: Define custom error codes?
+ }
+
+ AMediaCodec_stop(mDecoder);
+ AMediaCodec_stop(mEncoder);
+ return mStatus;
+}
+
+void VideoTrackTranscoder::abortTranscodeLoop() {
+ // Push abort message to the front of the codec event queue.
+ mCodecMessageQueue.push([this] { mStopRequested = true; }, true /* front */);
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSample.h b/media/libmediatranscoding/transcoder/include/media/MediaSample.h
index e206a3e..c2cef84 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaSample.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSample.h
@@ -38,21 +38,6 @@
SAMPLE_FLAG_PARTIAL_FRAME = 8,
};
-// Check that the sample flags have the expected NDK meaning.
-namespace {
-#include <media/NdkMediaCodec.h>
-#include <media/NdkMediaExtractor.h>
-
-static_assert(SAMPLE_FLAG_SYNC_SAMPLE == AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC,
- "Sample flag mismatch: SYNC_SAMPLE");
-static_assert(SAMPLE_FLAG_CODEC_CONFIG == AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG,
- "Sample flag mismatch: CODEC_CONFIG");
-static_assert(SAMPLE_FLAG_END_OF_STREAM == AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM,
- "Sample flag mismatch: END_OF_STREAM");
-static_assert(SAMPLE_FLAG_PARTIAL_FRAME == AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME,
- "Sample flag mismatch: PARTIAL_FRAME");
-} // anonymous namespace
-
/**
* MediaSampleInfo is an object that carries information about a compressed media sample without
* holding any sample data.
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
new file mode 100644
index 0000000..bbdbc1a
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 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 ANDROID_MEDIA_TRACK_TRANSCODER_H
+#define ANDROID_MEDIA_TRACK_TRANSCODER_H
+
+#include <media/MediaSampleQueue.h>
+#include <media/MediaSampleReader.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
+#include <utils/Mutex.h>
+
+#include <functional>
+#include <memory>
+#include <thread>
+
+namespace android {
+
+class MediaTrackTranscoder;
+
+/** Callback interface for MediaTrackTranscoder. */
+class MediaTrackTranscoderCallback {
+public:
+ /**
+ * Called when the MediaTrackTranscoder instance have finished transcoding all media samples
+ * successfully.
+ * @param transcoder The MediaTrackTranscoder that finished the transcoding.
+ */
+ virtual void onTrackFinished(MediaTrackTranscoder* transcoder);
+
+ /**
+ * Called when the MediaTrackTranscoder instance encountered an error it could not recover from.
+ * @param transcoder The MediaTrackTranscoder that encountered the error.
+ * @param status The non-zero error code describing the encountered error.
+ */
+ virtual void onTrackError(MediaTrackTranscoder* transcoder, media_status_t status);
+
+protected:
+ virtual ~MediaTrackTranscoderCallback() = default;
+};
+
+/**
+ * Base class for all track transcoders. MediaTrackTranscoder operates asynchronously on an internal
+ * thread and communicates through a MediaTrackTranscoderCallback instance. Transcoded samples are
+ * enqueued on the MediaTrackTranscoder's output queue. Samples need to be dequeued from the output
+ * queue or the transcoder will run out of buffers and stall. Once the consumer is done with a
+ * transcoded sample it is the consumer's responsibility to as soon as possible release all
+ * references to that sample in order to return the buffer to the transcoder. MediaTrackTranscoder
+ * is an abstract class and instances are created through one of the concrete subclasses.
+ *
+ * The base class MediaTrackTranscoder is responsible for thread and state management and guarantees
+ * that operations {configure, start, stop} are sent to the derived class in correct order.
+ * MediaTrackTranscoder is also responsible for delivering callback notifications once the
+ * transcoder has been successfully started.
+ */
+class MediaTrackTranscoder {
+public:
+ /**
+ * Configures the track transcoder with an input MediaSampleReader and a destination format.
+ * A track transcoder have to be configured before it is started.
+ * @param mediaSampleReader The MediaSampleReader to read input samples from.
+ * @param trackIndex The index of the track to transcode in mediaSampleReader.
+ * @param destinationFormat The destination format.
+ * @return AMEDIA_OK if the track transcoder was successfully configured.
+ */
+ media_status_t configure(const std::shared_ptr<MediaSampleReader>& mediaSampleReader,
+ int trackIndex,
+ const std::shared_ptr<AMediaFormat>& destinationFormat);
+
+ /**
+ * Starts the track transcoder. Once started the track transcoder have to be stopped by calling
+ * {@link #stop}, even after completing successfully. Start should only be called once.
+ * @return True if the track transcoder started, or false if it had already been started.
+ */
+ bool start();
+
+ /**
+ * Stops the track transcoder. Once the transcoding has been stopped it cannot be restarted
+ * again. It is safe to call stop multiple times.
+ * @return True if the track transcoder stopped, or false if it was already stopped.
+ */
+ bool stop();
+
+ /** Sample output queue. */
+ MediaSampleQueue mOutputQueue = {};
+
+protected:
+ MediaTrackTranscoder(const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback)
+ : mTranscoderCallback(transcoderCallback){};
+ virtual ~MediaTrackTranscoder() = default;
+
+ // configureDestinationFormat needs to be implemented by subclasses, and gets called on an
+ // external thread before start.
+ virtual media_status_t configureDestinationFormat(
+ const std::shared_ptr<AMediaFormat>& destinationFormat) = 0;
+
+ // runTranscodeLoop needs to be implemented by subclasses, and gets called on
+ // MediaTrackTranscoder's internal thread when the track transcoder is started.
+ virtual media_status_t runTranscodeLoop() = 0;
+
+ // abortTranscodeLoop needs to be implemented by subclasses, and should request transcoding to
+ // be aborted as soon as possible. It should be safe to call abortTranscodeLoop multiple times.
+ virtual void abortTranscodeLoop() = 0;
+
+ std::shared_ptr<MediaSampleReader> mMediaSampleReader;
+ int mTrackIndex;
+ std::shared_ptr<AMediaFormat> mSourceFormat;
+
+private:
+ const std::weak_ptr<MediaTrackTranscoderCallback> mTranscoderCallback;
+ std::mutex mStateMutex;
+ std::thread mTranscodingThread GUARDED_BY(mStateMutex);
+ enum {
+ UNINITIALIZED,
+ CONFIGURED,
+ STARTED,
+ STOPPED,
+ } mState GUARDED_BY(mStateMutex) = UNINITIALIZED;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRACK_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
new file mode 100644
index 0000000..7607c12
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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 ANDROID_VIDEO_TRACK_TRANSCODER_H
+#define ANDROID_VIDEO_TRACK_TRANSCODER_H
+
+#include <android/native_window.h>
+#include <media/MediaTrackTranscoder.h>
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaFormat.h>
+
+#include <condition_variable>
+#include <deque>
+#include <mutex>
+
+namespace android {
+
+/**
+ * Track transcoder for video tracks. VideoTrackTranscoder uses AMediaCodec from the Media NDK
+ * internally. The two media codecs are run in asynchronous mode and shares uncompressed buffers
+ * using a native surface (ANativeWindow). Codec callback events are placed on a message queue and
+ * serviced in order on the transcoding thread managed by MediaTrackTranscoder.
+ */
+class VideoTrackTranscoder : public MediaTrackTranscoder {
+public:
+ VideoTrackTranscoder(const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback)
+ : MediaTrackTranscoder(transcoderCallback){};
+ virtual ~VideoTrackTranscoder() override;
+
+private:
+ friend struct AsyncCodecCallbackDispatch;
+
+ // Minimal blocking queue used as a message queue by VideoTrackTranscoder.
+ template <typename T>
+ class BlockingQueue {
+ public:
+ void push(T const& value, bool front = false);
+ T pop();
+
+ private:
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ std::deque<T> mQueue;
+ };
+
+ // MediaTrackTranscoder
+ media_status_t runTranscodeLoop() override;
+ void abortTranscodeLoop() override;
+ media_status_t configureDestinationFormat(
+ const std::shared_ptr<AMediaFormat>& destinationFormat) override;
+ // ~MediaTrackTranscoder
+
+ // Enqueues an input sample with the decoder.
+ void enqueueInputSample(int32_t bufferIndex);
+
+ // Moves a decoded buffer from the decoder's output to the encoder's input.
+ void transferBuffer(int32_t bufferIndex, AMediaCodecBufferInfo bufferInfo);
+
+ // Dequeues an encoded buffer from the encoder and adds it to the output queue.
+ void dequeueOutputSample(int32_t bufferIndex, AMediaCodecBufferInfo bufferInfo);
+
+ // MediaSample release callback to return a buffer to the codec.
+ void releaseOutputSample(MediaSample* sample);
+
+ AMediaCodec* mDecoder = nullptr;
+ AMediaCodec* mEncoder = nullptr;
+ ANativeWindow* mSurface = nullptr;
+ bool mEOSFromSource = false;
+ bool mEOSFromEncoder = false;
+ bool mStopRequested = false;
+ media_status_t mStatus = AMEDIA_OK;
+ MediaSampleInfo mSampleInfo;
+ BlockingQueue<std::function<void()>> mCodecMessageQueue;
+ std::shared_ptr<AMediaFormat> mDestinationFormat;
+};
+
+} // namespace android
+#endif // ANDROID_VIDEO_TRACK_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/tests/Android.bp b/media/libmediatranscoding/transcoder/tests/Android.bp
index a9937d7..48449f8 100644
--- a/media/libmediatranscoding/transcoder/tests/Android.bp
+++ b/media/libmediatranscoding/transcoder/tests/Android.bp
@@ -44,3 +44,17 @@
defaults: ["testdefaults"],
srcs: ["MediaSampleQueueTests.cpp"],
}
+
+// MediaTrackTranscoder unit test
+cc_test {
+ name: "MediaTrackTranscoderTests",
+ defaults: ["testdefaults"],
+ srcs: ["MediaTrackTranscoderTests.cpp"],
+}
+
+// VideoTrackTranscoder unit test
+cc_test {
+ name: "VideoTrackTranscoderTests",
+ defaults: ["testdefaults"],
+ srcs: ["VideoTrackTranscoderTests.cpp"],
+}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
new file mode 100644
index 0000000..d1f3fee
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+// Unit Test for MediaTrackTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaTrackTranscoderTests"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/MediaTrackTranscoder.h>
+#include <media/VideoTrackTranscoder.h>
+
+#include "TrackTranscoderTestUtils.h"
+
+namespace android {
+
+/** TrackTranscoder types to test. */
+enum TrackTranscoderType {
+ VIDEO,
+};
+
+class MediaTrackTranscoderTests : public ::testing::TestWithParam<TrackTranscoderType> {
+public:
+ MediaTrackTranscoderTests() { LOG(DEBUG) << "MediaTrackTranscoderTests created"; }
+
+ void SetUp() override {
+ LOG(DEBUG) << "MediaTrackTranscoderTests set up";
+
+ mCallback = std::make_shared<TestCallback>();
+
+ switch (GetParam()) {
+ case VIDEO:
+ mTranscoder = std::make_shared<VideoTrackTranscoder>(mCallback);
+ ASSERT_NE(mTranscoder, nullptr);
+ break;
+ }
+ ASSERT_NE(mTranscoder, nullptr);
+
+ initSampleReader();
+ }
+
+ void initSampleReader() {
+ const char* sourcePath =
+ "/data/local/tmp/TranscoderTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+ const int sourceFd = open(sourcePath, O_RDONLY);
+ ASSERT_GT(sourceFd, 0);
+
+ const size_t fileSize = lseek(sourceFd, 0, SEEK_END);
+ lseek(sourceFd, 0, SEEK_SET);
+
+ mMediaSampleReader = MediaSampleReaderNDK::createFromFd(sourceFd, 0 /* offset */, fileSize);
+ ASSERT_NE(mMediaSampleReader, nullptr);
+ close(sourceFd);
+
+ for (size_t trackIndex = 0; trackIndex < mMediaSampleReader->getTrackCount();
+ ++trackIndex) {
+ AMediaFormat* trackFormat = mMediaSampleReader->getTrackFormat(trackIndex);
+ ASSERT_NE(trackFormat, nullptr);
+
+ const char* mime = nullptr;
+ AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+ ASSERT_NE(mime, nullptr);
+
+ if (GetParam() == VIDEO && strncmp(mime, "video/", 6) == 0) {
+ mTrackIndex = trackIndex;
+
+ mSourceFormat = std::shared_ptr<AMediaFormat>(
+ trackFormat, std::bind(AMediaFormat_delete, std::placeholders::_1));
+ ASSERT_NE(mSourceFormat, nullptr);
+
+ mDestinationFormat =
+ TrackTranscoderTestUtils::getDefaultVideoDestinationFormat(trackFormat);
+ ASSERT_NE(mDestinationFormat, nullptr);
+ break;
+ }
+
+ AMediaFormat_delete(trackFormat);
+ }
+
+ ASSERT_NE(mSourceFormat, nullptr);
+ }
+
+ // Drains the transcoder's output queue in a loop.
+ void drainOutputSampleQueue() {
+ mSampleQueueDrainThread = std::thread{[this] {
+ std::shared_ptr<MediaSample> sample;
+ bool aborted = false;
+ do {
+ aborted = mTranscoder->mOutputQueue.dequeue(&sample);
+ } while (!aborted && !(sample->info.flags & SAMPLE_FLAG_END_OF_STREAM));
+ mQueueWasAborted = aborted;
+ mGotEndOfStream =
+ sample != nullptr && (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) != 0;
+ }};
+ }
+
+ void joinDrainThread() {
+ if (mSampleQueueDrainThread.joinable()) {
+ mSampleQueueDrainThread.join();
+ }
+ }
+ void TearDown() override {
+ LOG(DEBUG) << "MediaTrackTranscoderTests tear down";
+ joinDrainThread();
+ }
+
+ ~MediaTrackTranscoderTests() { LOG(DEBUG) << "MediaTrackTranscoderTests destroyed"; }
+
+protected:
+ std::shared_ptr<MediaTrackTranscoder> mTranscoder;
+ std::shared_ptr<TestCallback> mCallback;
+
+ std::shared_ptr<MediaSampleReader> mMediaSampleReader;
+ int mTrackIndex;
+
+ std::shared_ptr<AMediaFormat> mSourceFormat;
+ std::shared_ptr<AMediaFormat> mDestinationFormat;
+
+ std::thread mSampleQueueDrainThread;
+ bool mQueueWasAborted = false;
+ bool mGotEndOfStream = false;
+};
+
+TEST_P(MediaTrackTranscoderTests, WaitNormalOperation) {
+ LOG(DEBUG) << "Testing WaitNormalOperation";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ ASSERT_TRUE(mTranscoder->start());
+ drainOutputSampleQueue();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+ EXPECT_TRUE(mTranscoder->stop());
+ joinDrainThread();
+ EXPECT_FALSE(mQueueWasAborted);
+ EXPECT_TRUE(mGotEndOfStream);
+}
+
+TEST_P(MediaTrackTranscoderTests, StopNormalOperation) {
+ LOG(DEBUG) << "Testing StopNormalOperation";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ EXPECT_TRUE(mTranscoder->start());
+ EXPECT_TRUE(mTranscoder->stop());
+}
+
+TEST_P(MediaTrackTranscoderTests, StartWithoutConfigure) {
+ LOG(DEBUG) << "Testing StartWithoutConfigure";
+ EXPECT_FALSE(mTranscoder->start());
+}
+
+TEST_P(MediaTrackTranscoderTests, StopWithoutStart) {
+ LOG(DEBUG) << "Testing StopWithoutStart";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ EXPECT_FALSE(mTranscoder->stop());
+}
+
+TEST_P(MediaTrackTranscoderTests, DoubleStartStop) {
+ LOG(DEBUG) << "Testing DoubleStartStop";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ EXPECT_TRUE(mTranscoder->start());
+ EXPECT_FALSE(mTranscoder->start());
+ EXPECT_TRUE(mTranscoder->stop());
+ EXPECT_FALSE(mTranscoder->stop());
+}
+
+TEST_P(MediaTrackTranscoderTests, DoubleConfigure) {
+ LOG(DEBUG) << "Testing DoubleConfigure";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_ERROR_UNSUPPORTED);
+}
+
+TEST_P(MediaTrackTranscoderTests, ConfigureAfterFail) {
+ LOG(DEBUG) << "Testing ConfigureAfterFail";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, -1, mDestinationFormat),
+ AMEDIA_ERROR_INVALID_PARAMETER);
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+}
+
+TEST_P(MediaTrackTranscoderTests, RestartAfterStop) {
+ LOG(DEBUG) << "Testing RestartAfterStop";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ EXPECT_TRUE(mTranscoder->start());
+ EXPECT_TRUE(mTranscoder->stop());
+ EXPECT_FALSE(mTranscoder->start());
+}
+
+TEST_P(MediaTrackTranscoderTests, RestartAfterFinish) {
+ LOG(DEBUG) << "Testing RestartAfterFinish";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ ASSERT_TRUE(mTranscoder->start());
+ drainOutputSampleQueue();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+ EXPECT_TRUE(mTranscoder->stop());
+ EXPECT_FALSE(mTranscoder->start());
+
+ joinDrainThread();
+ EXPECT_FALSE(mQueueWasAborted);
+ EXPECT_TRUE(mGotEndOfStream);
+}
+
+TEST_P(MediaTrackTranscoderTests, AbortOutputQueue) {
+ LOG(DEBUG) << "Testing AbortOutputQueue";
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+ ASSERT_TRUE(mTranscoder->start());
+ mTranscoder->mOutputQueue.abort();
+ drainOutputSampleQueue();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_ERROR_IO);
+ EXPECT_TRUE(mTranscoder->stop());
+
+ joinDrainThread();
+ EXPECT_TRUE(mQueueWasAborted);
+ EXPECT_FALSE(mGotEndOfStream);
+}
+
+TEST_P(MediaTrackTranscoderTests, NullSampleReader) {
+ LOG(DEBUG) << "Testing NullSampleReader";
+ std::shared_ptr<MediaSampleReader> nullSampleReader;
+ EXPECT_NE(mTranscoder->configure(nullSampleReader, mTrackIndex, mDestinationFormat), AMEDIA_OK);
+ EXPECT_FALSE(mTranscoder->start());
+}
+
+TEST_P(MediaTrackTranscoderTests, InvalidTrackIndex) {
+ LOG(DEBUG) << "Testing InvalidTrackIndex";
+ EXPECT_NE(mTranscoder->configure(mMediaSampleReader, -1, mDestinationFormat), AMEDIA_OK);
+ EXPECT_NE(mTranscoder->configure(mMediaSampleReader, mMediaSampleReader->getTrackCount(),
+ mDestinationFormat),
+ AMEDIA_OK);
+}
+
+}; // namespace android
+
+using namespace android;
+
+INSTANTIATE_TEST_SUITE_P(MediaTrackTranscoderTestsAll, MediaTrackTranscoderTests,
+ ::testing::Values(VIDEO));
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
new file mode 100644
index 0000000..6b9131c
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <media/MediaTrackTranscoder.h>
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+
+namespace android {
+
+//
+// This file contains test utilities used by more than one track transcoder test.
+//
+
+class TrackTranscoderTestUtils {
+public:
+ static std::shared_ptr<AMediaFormat> getDefaultVideoDestinationFormat(
+ AMediaFormat* sourceFormat) {
+ // Default video destination format setup.
+ static constexpr float kFrameRate = 30.0f;
+ static constexpr float kIFrameInterval = 30.0f;
+ static constexpr int32_t kBitRate = 2 * 1000 * 1000;
+ static constexpr int32_t kColorFormatSurface = 0x7f000789;
+
+ AMediaFormat* destinationFormat = AMediaFormat_new();
+ AMediaFormat_copy(destinationFormat, sourceFormat);
+ AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_FRAME_RATE, kFrameRate);
+ AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
+ kIFrameInterval);
+ AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
+ AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
+ kColorFormatSurface);
+
+ return std::shared_ptr<AMediaFormat>(destinationFormat,
+ std::bind(AMediaFormat_delete, std::placeholders::_1));
+ }
+};
+
+class TestCallback : public MediaTrackTranscoderCallback {
+public:
+ TestCallback() = default;
+ ~TestCallback() = default;
+
+ // MediaTrackTranscoderCallback
+ void onTrackFinished(MediaTrackTranscoder* transcoder __unused) {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mTranscodingFinished = true;
+ mCv.notify_all();
+ }
+
+ void onTrackError(MediaTrackTranscoder* transcoder __unused, media_status_t status) {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mTranscodingFinished = true;
+ mStatus = status;
+ mCv.notify_all();
+ }
+ // ~MediaTrackTranscoderCallback
+
+ media_status_t waitUntilFinished() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (!mTranscodingFinished) {
+ mCv.wait(lock);
+ }
+ return mStatus;
+ }
+
+private:
+ media_status_t mStatus = AMEDIA_OK;
+ std::mutex mMutex;
+ std::condition_variable mCv;
+ bool mTranscodingFinished = false;
+};
+
+}; // namespace android
diff --git a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
new file mode 100644
index 0000000..3cec1a1
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+// Unit Test for VideoTrackTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VideoTrackTranscoderTests"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/VideoTrackTranscoder.h>
+#include <utils/Timers.h>
+
+#include "TrackTranscoderTestUtils.h"
+
+namespace android {
+
+// TODO(b/155304421): Implement more advanced video specific tests:
+// - Codec conversions (HEVC -> AVC).
+// - Bitrate validation.
+// - Output frame validation through PSNR.
+
+class VideoTrackTranscoderTests : public ::testing::Test {
+public:
+ VideoTrackTranscoderTests() { LOG(DEBUG) << "VideoTrackTranscoderTests created"; }
+
+ void SetUp() override {
+ LOG(DEBUG) << "VideoTrackTranscoderTests set up";
+ const char* sourcePath =
+ "/data/local/tmp/TranscoderTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+ const int sourceFd = open(sourcePath, O_RDONLY);
+ ASSERT_GT(sourceFd, 0);
+
+ const off_t fileSize = lseek(sourceFd, 0, SEEK_END);
+ lseek(sourceFd, 0, SEEK_SET);
+
+ mMediaSampleReader = MediaSampleReaderNDK::createFromFd(sourceFd, 0, fileSize);
+ ASSERT_NE(mMediaSampleReader, nullptr);
+ close(sourceFd);
+
+ for (size_t trackIndex = 0; trackIndex < mMediaSampleReader->getTrackCount();
+ ++trackIndex) {
+ AMediaFormat* trackFormat = mMediaSampleReader->getTrackFormat(trackIndex);
+ ASSERT_NE(trackFormat, nullptr);
+
+ const char* mime = nullptr;
+ AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+ ASSERT_NE(mime, nullptr);
+
+ if (strncmp(mime, "video/", 6) == 0) {
+ mTrackIndex = trackIndex;
+
+ mSourceFormat = std::shared_ptr<AMediaFormat>(
+ trackFormat, std::bind(AMediaFormat_delete, std::placeholders::_1));
+ ASSERT_NE(mSourceFormat, nullptr);
+
+ mDestinationFormat =
+ TrackTranscoderTestUtils::getDefaultVideoDestinationFormat(trackFormat);
+ ASSERT_NE(mDestinationFormat, nullptr);
+ break;
+ }
+
+ AMediaFormat_delete(trackFormat);
+ }
+
+ ASSERT_NE(mSourceFormat, nullptr);
+ }
+
+ void TearDown() override { LOG(DEBUG) << "VideoTrackTranscoderTests tear down"; }
+
+ ~VideoTrackTranscoderTests() { LOG(DEBUG) << "VideoTrackTranscoderTests destroyed"; }
+
+ std::shared_ptr<MediaSampleReader> mMediaSampleReader;
+ int mTrackIndex;
+ std::shared_ptr<AMediaFormat> mSourceFormat;
+ std::shared_ptr<AMediaFormat> mDestinationFormat;
+};
+
+TEST_F(VideoTrackTranscoderTests, SampleSanity) {
+ LOG(DEBUG) << "Testing SampleSanity";
+ std::shared_ptr<TestCallback> callback = std::make_shared<TestCallback>();
+ VideoTrackTranscoder transcoder{callback};
+
+ EXPECT_EQ(transcoder.configure(mMediaSampleReader, mTrackIndex, mDestinationFormat), AMEDIA_OK);
+ ASSERT_TRUE(transcoder.start());
+
+ std::thread sampleConsumerThread{[&transcoder] {
+ uint64_t sampleCount = 0;
+ std::shared_ptr<MediaSample> sample;
+ while (!transcoder.mOutputQueue.dequeue(&sample)) {
+ ASSERT_NE(sample, nullptr);
+ const uint32_t flags = sample->info.flags;
+
+ if (sampleCount == 0) {
+ // Expect first sample to be a codec config.
+ EXPECT_TRUE((flags & SAMPLE_FLAG_CODEC_CONFIG) != 0);
+ EXPECT_TRUE((flags & SAMPLE_FLAG_SYNC_SAMPLE) == 0);
+ EXPECT_TRUE((flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
+ EXPECT_TRUE((flags & SAMPLE_FLAG_PARTIAL_FRAME) == 0);
+ } else if (sampleCount == 1) {
+ // Expect second sample to be a sync sample.
+ EXPECT_TRUE((flags & SAMPLE_FLAG_CODEC_CONFIG) == 0);
+ EXPECT_TRUE((flags & SAMPLE_FLAG_SYNC_SAMPLE) != 0);
+ EXPECT_TRUE((flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
+ }
+
+ if (!(flags & SAMPLE_FLAG_END_OF_STREAM)) {
+ // Expect a valid buffer unless it is EOS.
+ EXPECT_NE(sample->buffer, nullptr);
+ EXPECT_NE(sample->bufferId, 0xBAADF00D);
+ EXPECT_GT(sample->info.size, 0);
+ }
+
+ ++sampleCount;
+ if (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) {
+ break;
+ }
+ sample.reset();
+ }
+ }};
+
+ EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
+ EXPECT_TRUE(transcoder.stop());
+
+ sampleConsumerThread.join();
+}
+
+// VideoTrackTranscoder needs a valid destination format.
+TEST_F(VideoTrackTranscoderTests, NullDestinationFormat) {
+ LOG(DEBUG) << "Testing NullDestinationFormat";
+ std::shared_ptr<TestCallback> callback = std::make_shared<TestCallback>();
+ std::shared_ptr<AMediaFormat> nullFormat;
+
+ VideoTrackTranscoder transcoder{callback};
+ EXPECT_EQ(transcoder.configure(mMediaSampleReader, 0 /* trackIndex */, nullFormat),
+ AMEDIA_ERROR_INVALID_PARAMETER);
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
index dbee604..3c30e46 100755
--- a/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
+++ b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
@@ -27,3 +27,9 @@
echo "testing MediaSampleQueue"
adb shell /data/nativetest64/MediaSampleQueueTests/MediaSampleQueueTests
+
+echo "testing MediaTrackTranscoder"
+adb shell /data/nativetest64/MediaTrackTranscoderTests/MediaTrackTranscoderTests
+
+echo "testing VideoTrackTranscoder"
+adb shell /data/nativetest64/VideoTrackTranscoderTests/VideoTrackTranscoderTests
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index b597583..3ba87d6 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -56,6 +56,7 @@
#include <media/stagefright/CCodec.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecList.h>
+#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaFilter.h>
@@ -199,6 +200,7 @@
void addResource(const MediaResourceParcel &resource);
void removeResource(const MediaResourceParcel &resource);
void removeClient();
+ void markClientForPendingRemoval();
bool reclaimResource(const std::vector<MediaResourceParcel> &resources);
private:
@@ -280,6 +282,14 @@
mService->removeClient(mPid, getId(mClient));
}
+void MediaCodec::ResourceManagerServiceProxy::markClientForPendingRemoval() {
+ Mutex::Autolock _l(mLock);
+ if (mService == nullptr) {
+ return;
+ }
+ mService->markClientForPendingRemoval(mPid, getId(mClient));
+}
+
bool MediaCodec::ResourceManagerServiceProxy::reclaimResource(
const std::vector<MediaResourceParcel> &resources) {
Mutex::Autolock _l(mLock);
@@ -297,6 +307,33 @@
////////////////////////////////////////////////////////////////////////////////
+class MediaCodec::ReleaseSurface {
+public:
+ ReleaseSurface() {
+ BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+ mSurface = new Surface(mProducer, false /* controlledByApp */);
+ struct ConsumerListener : public BnConsumerListener {
+ void onFrameAvailable(const BufferItem&) override {}
+ void onBuffersReleased() override {}
+ void onSidebandStreamChanged() override {}
+ };
+ sp<ConsumerListener> listener{new ConsumerListener};
+ mConsumer->consumerConnect(listener, false);
+ mConsumer->setConsumerName(String8{"MediaCodec.release"});
+ }
+
+ const sp<Surface> &getSurface() {
+ return mSurface;
+ }
+
+private:
+ sp<IGraphicBufferProducer> mProducer;
+ sp<IGraphicBufferConsumer> mConsumer;
+ sp<Surface> mSurface;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
namespace {
enum {
@@ -1432,7 +1469,13 @@
status_t MediaCodec::release() {
sp<AMessage> msg = new AMessage(kWhatRelease, this);
+ sp<AMessage> response;
+ return PostAndAwaitResponse(msg, &response);
+}
+status_t MediaCodec::releaseAsync() {
+ sp<AMessage> msg = new AMessage(kWhatRelease, this);
+ msg->setInt32("async", 1);
sp<AMessage> response;
return PostAndAwaitResponse(msg, &response);
}
@@ -2263,7 +2306,7 @@
if (mSurface != nullptr && !mAllowFrameDroppingBySurface) {
// signal frame dropping mode in the input format as this may also be
// meaningful and confusing for an encoder in a transcoder scenario
- mInputFormat->setInt32("allow-frame-drop", mAllowFrameDroppingBySurface);
+ mInputFormat->setInt32(KEY_ALLOW_FRAME_DROP, mAllowFrameDroppingBySurface);
}
ALOGV("[%s] configured as input format: %s, output format: %s",
mComponentName.c_str(),
@@ -2600,7 +2643,9 @@
mResourceManagerProxy->removeClient();
- (new AMessage)->postReply(mReplyID);
+ if (mReplyID != nullptr) {
+ (new AMessage)->postReply(mReplyID);
+ }
break;
}
@@ -2720,7 +2765,7 @@
}
if (obj != NULL) {
- if (!format->findInt32("allow-frame-drop", &mAllowFrameDroppingBySurface)) {
+ if (!format->findInt32(KEY_ALLOW_FRAME_DROP, &mAllowFrameDroppingBySurface)) {
// allow frame dropping by surface by default
mAllowFrameDroppingBySurface = true;
}
@@ -2987,6 +3032,26 @@
break;
}
+ int32_t async = 0;
+ if (msg->findInt32("async", &async) && async) {
+ if ((mState == CONFIGURED || mState == STARTED || mState == FLUSHED)
+ && mSurface != NULL) {
+ if (!mReleaseSurface) {
+ mReleaseSurface.reset(new ReleaseSurface);
+ }
+ status_t err = connectToSurface(mReleaseSurface->getSurface());
+ ALOGW_IF(err != OK, "error connecting to release surface: err = %d", err);
+ if (err == OK && !(mFlags & kFlagUsesSoftwareRenderer)) {
+ err = mCodec->setSurface(mReleaseSurface->getSurface());
+ ALOGW_IF(err != OK, "error setting release surface: err = %d", err);
+ }
+ if (err == OK) {
+ (void)disconnectFromSurface();
+ mSurface = mReleaseSurface->getSurface();
+ }
+ }
+ }
+
mReplyID = replyID;
setState(msg->what() == kWhatStop ? STOPPING : RELEASING);
@@ -2999,6 +3064,12 @@
pushBlankBuffersToNativeWindow(mSurface.get());
}
+ if (async) {
+ mResourceManagerProxy->markClientForPendingRemoval();
+ (new AMessage)->postReply(mReplyID);
+ mReplyID = 0;
+ }
+
break;
}
diff --git a/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp b/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp
index d4e7e5c..c7a7378 100644
--- a/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp
+++ b/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp
@@ -26,8 +26,10 @@
constexpr int32_t kBitsPerSample = 16;
constexpr int32_t kOutputBufferSize = kSamplesPerFrame * kBitsPerSample / 8;
const bitstream_format kBitStreamFormats[2] = {MIME_IETF, IF2};
-const int32_t kLocalWmfDecBytesPerFrame[8] = {12, 13, 15, 17, 19, 20, 26, 31};
-const int32_t kLocalIf2DecBytesPerFrame[8] = {13, 14, 16, 18, 19, 21, 26, 31};
+const int32_t kLocalWmfDecBytesPerFrame[16] = {12, 13, 15, 17, 19, 20, 26, 31,
+ 5, 6, 5, 5, 0, 0, 0, 0};
+const int32_t kLocalIf2DecBytesPerFrame[16] = {13, 14, 16, 18, 19, 21, 26, 31,
+ 13, 14, 16, 18, 19, 21, 26, 31};
class Codec {
public:
@@ -52,7 +54,7 @@
bitstream_format bitsreamFormat = kBitStreamFormats[bit];
int32_t frameSize = 0;
/* Find frame type */
- Frame_Type_3GPP frameType = static_cast<Frame_Type_3GPP>((mode >> 3) & 0x07);
+ Frame_Type_3GPP frameType = static_cast<Frame_Type_3GPP>((mode >> 3) & 0x0f);
++data;
--size;
if (bit) {
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp
index 679b091..a11f55e 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp
@@ -409,7 +409,9 @@
if (!BitstreamRead1Bits(stream)) return PV_FAIL;
/* video_object_layer_width (13 bits) */
- video->displayWidth = video->width = (int) BitstreamReadBits16(stream, 13);
+ tmpvar = BitstreamReadBits16(stream, 13);
+ if (!tmpvar) return PV_FAIL;
+ video->displayWidth = video->width = tmpvar;
/* round up to a multiple of MB_SIZE. 08/09/2000 */
video->width = (video->width + 15) & -16;
@@ -419,7 +421,9 @@
if (!BitstreamRead1Bits(stream)) return PV_FAIL;
/* video_object_layer_height (13 bits) */
- video->displayHeight = video->height = (int) BitstreamReadBits16(stream, 13);
+ tmpvar = BitstreamReadBits16(stream, 13);
+ if (!tmpvar) return PV_FAIL;
+ video->displayHeight = video->height = tmpvar;
/* round up to a multiple of MB_SIZE. 08/09/2000 */
video->height = (video->height + 15) & -16;
diff --git a/media/libstagefright/id3/Android.bp b/media/libstagefright/id3/Android.bp
index c8173cf..02de2c0 100644
--- a/media/libstagefright/id3/Android.bp
+++ b/media/libstagefright/id3/Android.bp
@@ -4,7 +4,9 @@
srcs: ["ID3.cpp"],
header_libs: [
- "libmedia_headers",
+ "libmedia_datasource_headers",
+ "libstagefright_foundation_headers",
+ "libstagefright_headers",
"media_ndk_headers",
],
@@ -18,6 +20,12 @@
],
cfi: true,
},
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
//###############################################################################
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 736f626..c6b6639 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -146,6 +146,8 @@
// object.
status_t release();
+ status_t releaseAsync();
+
status_t flush();
status_t queueInputBuffer(
@@ -516,6 +518,9 @@
// when low latency is on
int64_t mInputBufferCounter; // number of input buffers queued since last reset/flush
+ class ReleaseSurface;
+ std::unique_ptr<ReleaseSurface> mReleaseSurface;
+
sp<BatteryChecker> mBatteryChecker;
void statsBufferSent(int64_t presentationUs);
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecConstants.h b/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
index 178d334..9c67338 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
@@ -744,6 +744,7 @@
constexpr char KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT[] = "aac-max-output-channel_count";
constexpr char KEY_AAC_PROFILE[] = "aac-profile";
constexpr char KEY_AAC_SBR_MODE[] = "aac-sbr-mode";
+constexpr char KEY_ALLOW_FRAME_DROP[] = "allow-frame-drop";
constexpr char KEY_AUDIO_SESSION_ID[] = "audio-session-id";
constexpr char KEY_BIT_RATE[] = "bitrate";
constexpr char KEY_BITRATE_MODE[] = "bitrate-mode";
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 657144c..4bb21fa 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -1153,7 +1153,7 @@
}
const RangeInfo &info = *mRangeInfos.begin();
- if (mBuffer->size() < info.mLength) {
+ if (info.mLength == 0 || mBuffer->size() < info.mLength) {
return NULL;
}
diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp
index 4502f62..5b5b4b1 100644
--- a/media/libstagefright/rtsp/ASessionDescription.cpp
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -27,6 +27,8 @@
namespace android {
+constexpr unsigned kDefaultAs = 960; // kbps?
+
ASessionDescription::ASessionDescription()
: mIsValid(false) {
}
@@ -401,7 +403,7 @@
sdp.append("\r\n");
sdp.append("b=AS:");
- sdp.append(as > 0 ? as : 960);
+ sdp.append(as > 0 ? as : kDefaultAs);
sdp.append("\r\n");
sdp.append("a=rtpmap:");
@@ -420,7 +422,7 @@
sdp.append("\r\n");
}
- if (width > 0 && height > 0) {
+ if (!isAudio && width > 0 && height > 0) {
sdp.append("a=framesize:");
sdp.append(payloadType);
sdp.append(" ");
diff --git a/media/libstagefright/rtsp/Android.bp b/media/libstagefright/rtsp/Android.bp
index 84a47af..6179142 100644
--- a/media/libstagefright/rtsp/Android.bp
+++ b/media/libstagefright/rtsp/Android.bp
@@ -22,7 +22,6 @@
],
shared_libs: [
- "libandroid",
"libandroid_net",
"libcrypto",
"libdatasource",
diff --git a/media/mediaserver/mediaserver.rc b/media/mediaserver/mediaserver.rc
index ecb75a9..05373c9 100644
--- a/media/mediaserver/mediaserver.rc
+++ b/media/mediaserver/mediaserver.rc
@@ -6,4 +6,4 @@
user media
group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm
ioprio rt 4
- writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+ task_profiles ProcessCapacityHigh HighPerformance
diff --git a/media/ndk/Android.bp b/media/ndk/Android.bp
index 80941e0..f365af3 100644
--- a/media/ndk/Android.bp
+++ b/media/ndk/Android.bp
@@ -196,3 +196,41 @@
"frameworks/av/media/ndk/",
],
}
+
+cc_library_static {
+ name: "libmediandk_format",
+
+ host_supported: true,
+
+ srcs: [
+ "NdkMediaFormat.cpp",
+ ],
+
+ header_libs: [
+ "libstagefright_foundation_headers",
+ ],
+
+ cflags: [
+ "-DEXPORT=__attribute__((visibility(\"default\")))",
+ "-Werror",
+ "-Wall",
+ ],
+
+ export_include_dirs: ["include"],
+
+ sanitize: {
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ },
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
+ apex_available: ["com.android.media"],
+}
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index ab0cb63..8680641 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -26,9 +26,6 @@
#include <utils/StrongPointer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
-#include <android_util_Binder.h>
-
-#include <jni.h>
using namespace android;
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 3fd55d4..ef7b1ab 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -88,6 +88,8 @@
#include "SpdifStreamOut.h"
#include "AudioHwDevice.h"
#include "NBAIO_Tee.h"
+#include "ThreadMetrics.h"
+#include "TrackMetrics.h"
#include <android/os/IPowerManager.h>
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index 1ff03c4..d8eebf3 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -171,6 +171,16 @@
void setTeePatches(TeePatches teePatches);
+ void tallyUnderrunFrames(size_t frames) override {
+ if (isOut()) { // we expect this from output tracks only
+ mAudioTrackServerProxy->tallyUnderrunFrames(frames);
+ // Fetch absolute numbers from AudioTrackShared as it counts
+ // contiguous underruns as a one -- we want a consistent number.
+ // TODO: isolate this counting into a class.
+ mTrackMetrics.logUnderruns(mAudioTrackServerProxy->getUnderrunCount(),
+ mAudioTrackServerProxy->getUnderrunFrames());
+ }
+ }
protected:
// for numerous
friend class PlaybackThread;
diff --git a/services/audioflinger/ThreadMetrics.h b/services/audioflinger/ThreadMetrics.h
new file mode 100644
index 0000000..7989de1
--- /dev/null
+++ b/services/audioflinger/ThreadMetrics.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2020 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 ANDROID_AUDIO_THREADMETRICS_H
+#define ANDROID_AUDIO_THREADMETRICS_H
+
+#include <mutex>
+
+namespace android {
+
+/**
+ * ThreadMetrics handles the AudioFlinger thread log statistics.
+ *
+ * We aggregate metrics for a particular device for proper analysis.
+ * This includes power, performance, and usage metrics.
+ *
+ * This class is thread-safe with a lock for safety. There is no risk of deadlock
+ * as this class only executes external one-way calls in Mediametrics and does not
+ * call any other AudioFlinger class.
+ *
+ * Terminology:
+ * An AudioInterval is a contiguous playback segment.
+ * An AudioIntervalGroup is a group of continuous playback segments on the same device.
+ *
+ * We currently deliver metrics based on an AudioIntervalGroup.
+ */
+class ThreadMetrics final {
+public:
+ ThreadMetrics(std::string metricsId, bool isOut)
+ : mMetricsId(std::move(metricsId))
+ , mIsOut(isOut)
+ {}
+
+ ~ThreadMetrics() {
+ logEndInterval(); // close any open interval groups
+ std::lock_guard l(mLock);
+ deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP);
+ mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_DTOR)
+ .record();
+ }
+
+ // Called under the following circumstances
+ // 1) Upon a createPatch and we are not in standby
+ // 2) We come out of standby
+ void logBeginInterval() {
+ std::lock_guard l(mLock);
+ if (mDevices != mCreatePatchDevices) {
+ deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP);
+ mDevices = mCreatePatchDevices; // set after endAudioIntervalGroup
+ resetIntervalGroupMetrics();
+ deliverDeviceMetrics(
+ AMEDIAMETRICS_PROP_EVENT_VALUE_BEGINAUDIOINTERVALGROUP, mDevices.c_str());
+ }
+ if (mIntervalStartTimeNs == 0) {
+ ++mIntervalCount;
+ mIntervalStartTimeNs = systemTime();
+ }
+ }
+
+ void logConstructor(pid_t pid, const char *threadType, int32_t id) const {
+ mediametrics::LogItem(mMetricsId)
+ .setPid(pid)
+ .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
+ .set(AMEDIAMETRICS_PROP_TYPE, threadType)
+ .set(AMEDIAMETRICS_PROP_THREADID, id)
+ .record();
+ }
+
+ void logCreatePatch(const std::string& devices) {
+ std::lock_guard l(mLock);
+ mCreatePatchDevices = devices;
+ mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CREATEAUDIOPATCH)
+ .set(AMEDIAMETRICS_PROP_OUTPUTDEVICES, devices)
+ .record();
+ }
+
+ // 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;
+ }
+ }
+
+ void logThrottleMs(double throttleMs) const {
+ mediametrics::LogItem(mMetricsId)
+ // ms units always double
+ .set(AMEDIAMETRICS_PROP_THROTTLEMS, (double)throttleMs)
+ .record();
+ }
+
+ void logLatency(double latencyMs) {
+ mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_LATENCYMS, latencyMs)
+ .record();
+ std::lock_guard l(mLock);
+ mDeviceLatencyMs.add(latencyMs);
+ }
+
+ // TODO: further implement this.
+ void logUnderrunFrames(size_t count, size_t frames) {
+ std::lock_guard l(mLock);
+ mUnderrunCount = count;
+ mUnderrunFrames = frames;
+ }
+
+ const std::string& getMetricsId() const {
+ return mMetricsId;
+ }
+
+private:
+ // no lock required - all arguments and constants.
+ void deliverDeviceMetrics(const char *eventName, const char *devices) const {
+ mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT, eventName)
+ .set(mIsOut ? AMEDIAMETRICS_PROP_OUTPUTDEVICES
+ : AMEDIAMETRICS_PROP_INPUTDEVICES, devices)
+ .record();
+ }
+
+ void deliverCumulativeMetrics(const char *eventName) const REQUIRES(mLock) {
+ if (mIntervalCount > 0) {
+ mediametrics::LogItem item(mMetricsId);
+ item.set(AMEDIAMETRICS_PROP_CUMULATIVETIMENS, mCumulativeTimeNs)
+ .set(AMEDIAMETRICS_PROP_DEVICETIMENS, mDeviceTimeNs)
+ .set(AMEDIAMETRICS_PROP_EVENT, eventName)
+ .set(AMEDIAMETRICS_PROP_INTERVALCOUNT, (int32_t)mIntervalCount);
+ if (mDeviceLatencyMs.getN() > 0) {
+ item.set(AMEDIAMETRICS_PROP_DEVICELATENCYMS, mDeviceLatencyMs.getMean());
+ }
+ if (mUnderrunCount > 0) {
+ item.set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t)mUnderrunCount)
+ .set(AMEDIAMETRICS_PROP_UNDERRUNFRAMES, (int64_t)mUnderrunFrames);
+ }
+ item.record();
+ }
+ }
+
+ void resetIntervalGroupMetrics() REQUIRES(mLock) {
+ // mDevices is not reset by clear
+
+ mIntervalCount = 0;
+ mIntervalStartTimeNs = 0;
+ // mCumulativeTimeNs is not reset by clear.
+ mDeviceTimeNs = 0;
+
+ mDeviceLatencyMs.reset();
+
+ mUnderrunCount = 0;
+ mUnderrunFrames = 0;
+ }
+
+ const std::string mMetricsId;
+ const bool mIsOut; // if true, than a playback track, otherwise used for record.
+
+ mutable std::mutex mLock;
+
+ // Devices in the interval group.
+ std::string mDevices GUARDED_BY(mLock);
+ std::string mCreatePatchDevices GUARDED_BY(mLock);
+
+ // 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;
+
+ // latency and startup for each interval.
+ audio_utils::Statistics<double> mDeviceLatencyMs GUARDED_BY(mLock);
+
+ // underrun count and frames
+ int64_t mUnderrunCount GUARDED_BY(mLock) = 0;
+ int64_t mUnderrunFrames GUARDED_BY(mLock) = 0;
+};
+
+} // namespace android
+
+#endif // ANDROID_AUDIO_THREADMETRICS_H
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 0e96522..5266192 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -220,6 +220,9 @@
{
std::stringstream ss;
for (size_t i = 0; i < patch->num_sinks; ++i) {
+ if (i > 0) {
+ ss << "|";
+ }
ss << "(" << toString(patch->sinks[i].ext.device.type)
<< ", " << patch->sinks[i].ext.device.address << ")";
}
@@ -230,6 +233,9 @@
{
std::stringstream ss;
for (size_t i = 0; i < patch->num_sources; ++i) {
+ if (i > 0) {
+ ss << "|";
+ }
ss << "(" << toString(patch->sources[i].ext.device.type)
<< ", " << patch->sources[i].ext.device.address << ")";
}
@@ -486,11 +492,13 @@
}
AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
- type_t type, bool systemReady)
+ type_t type, bool systemReady, bool isOut)
: Thread(false /*canCallJava*/),
mType(type),
mAudioFlinger(audioFlinger),
- mMetricsId(std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD) + std::to_string(id)),
+ mThreadMetrics(std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD) + std::to_string(id),
+ isOut),
+ mIsOut(isOut),
// mSampleRate, mFrameCount, mChannelMask, mChannelCount, mFrameSize, mFormat, mBufferSize
// are set by PlaybackThread::readOutputParameters_l() or
// RecordThread::readInputParameters_l()
@@ -502,13 +510,7 @@
mSystemReady(systemReady),
mSignalPending(false)
{
- mediametrics::LogItem(mMetricsId)
- .setPid(getpid())
- .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
- .set(AMEDIAMETRICS_PROP_TYPE, threadTypeToString(type))
- .set(AMEDIAMETRICS_PROP_THREADID, id)
- .record();
-
+ mThreadMetrics.logConstructor(getpid(), threadTypeToString(type), id);
memset(&mPatch, 0, sizeof(struct audio_patch));
}
@@ -525,10 +527,6 @@
}
sendStatistics(true /* force */);
-
- mediametrics::LogItem(mMetricsId)
- .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_DTOR)
- .record();
}
status_t AudioFlinger::ThreadBase::readyToRun()
@@ -1685,6 +1683,7 @@
#ifdef TEE_SINK
track->dumpTee(-1 /* fd */, "_REMOVE");
#endif
+ track->logEndInterval(); // log to MediaMetrics
return index;
}
@@ -1827,7 +1826,7 @@
audio_io_handle_t id,
type_t type,
bool systemReady)
- : ThreadBase(audioFlinger, id, type, systemReady),
+ : ThreadBase(audioFlinger, id, type, systemReady, true /* isOut */),
mNormalFrameCount(0), mSinkBuffer(NULL),
mMixerBufferEnabled(AudioFlinger::kEnableExtendedPrecision),
mMixerBuffer(NULL),
@@ -2552,6 +2551,7 @@
chain->incActiveTrackCnt();
}
+ track->logBeginInterval(patchSinksToString(&mPatch)); // log to MediaMetrics
status = NO_ERROR;
}
@@ -2875,7 +2875,7 @@
}
audio_output_flags_t flags = mOutput->flags;
- mediametrics::LogItem item(mMetricsId);
+ mediametrics::LogItem item(mThreadMetrics.getMetricsId()); // TODO: method in ThreadMetrics?
item.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_READPARAMETERS)
.set(AMEDIAMETRICS_PROP_ENCODING, formatToString(mFormat).c_str())
.set(AMEDIAMETRICS_PROP_SAMPLERATE, (int32_t)mSampleRate)
@@ -3050,6 +3050,10 @@
{
if (!mMasterMute) {
char value[PROPERTY_VALUE_MAX];
+ if (mOutDeviceTypeAddrs.empty()) {
+ ALOGD("ro.audio.silent is ignored since no output device is set");
+ return;
+ }
if (isSingleDeviceType(outDeviceTypes(), AUDIO_DEVICE_OUT_REMOTE_SUBMIX)) {
ALOGD("ro.audio.silent will be ignored for threads on AUDIO_DEVICE_OUT_REMOTE_SUBMIX");
return;
@@ -3129,7 +3133,10 @@
mNumWrites++;
mInWrite = false;
- mStandby = false;
+ if (mStandby) {
+ mThreadMetrics.logBeginInterval();
+ mStandby = false;
+ }
return bytesWritten;
}
@@ -3668,8 +3675,9 @@
// This is where we go into standby
if (!mStandby) {
LOG_AUDIO_STATE();
+ mThreadMetrics.logEndInterval();
+ mStandby = true;
}
- mStandby = true;
sendStatistics(false /* force */);
}
@@ -3973,10 +3981,7 @@
const int32_t throttleMs = (int32_t)mHalfBufferMs - deltaMs;
if ((signed)mHalfBufferMs >= throttleMs && throttleMs > 0) {
- mediametrics::LogItem(mMetricsId)
- // ms units always double
- .set(AMEDIAMETRICS_PROP_THROTTLEMS, (double)throttleMs)
- .record();
+ mThreadMetrics.logThrottleMs((double)throttleMs);
usleep(throttleMs * 1000);
// notify of throttle start on verbose log
@@ -4226,6 +4231,7 @@
(mPatch.sinks[0].id != sinkPortId);
mPatch = *patch;
mOutDeviceTypeAddrs = deviceTypeAddrs;
+ checkSilentMode_l();
if (mOutput->audioHwDev->supportsAudioPatches()) {
sp<DeviceHalInterface> hwDevice = mOutput->audioHwDev->hwDevice();
@@ -4250,10 +4256,16 @@
status = mOutput->stream->setParameters(param.toString());
*handle = AUDIO_PATCH_HANDLE_NONE;
}
- mediametrics::LogItem(mMetricsId)
- .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CREATEAUDIOPATCH)
- .set(AMEDIAMETRICS_PROP_OUTPUTDEVICES, patchSinksToString(patch).c_str())
- .record();
+ const std::string patchSinksAsString = patchSinksToString(patch);
+
+ mThreadMetrics.logEndInterval();
+ mThreadMetrics.logCreatePatch(patchSinksAsString);
+ mThreadMetrics.logBeginInterval();
+ // also dispatch to active AudioTracks for MediaMetrics
+ for (const auto &track : mActiveTracks) {
+ track->logEndInterval();
+ track->logBeginInterval(patchSinksAsString);
+ }
if (configChanged) {
sendIoConfigEvent_l(AUDIO_OUTPUT_CONFIG_CHANGED);
@@ -4796,9 +4808,8 @@
// DeferredOperations handles statistics after setting mixerStatus.
class DeferredOperations {
public:
- DeferredOperations(mixer_state *mixerStatus, const std::string &metricsId)
- : mMixerStatus(mixerStatus)
- , mMetricsId(metricsId) {}
+ explicit DeferredOperations(mixer_state *mixerStatus)
+ : mMixerStatus(mixerStatus) {}
// when leaving scope, tally frames properly.
~DeferredOperations() {
@@ -4806,19 +4817,9 @@
// because that is when the underrun occurs.
// We do not distinguish between FastTracks and NormalTracks here.
if (*mMixerStatus == MIXER_TRACKS_READY && mUnderrunFrames.size() > 0) {
- mediametrics::LogItem item(mMetricsId);
-
- item.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_UNDERRUN);
for (const auto &underrun : mUnderrunFrames) {
- underrun.first->mAudioTrackServerProxy->tallyUnderrunFrames(
- underrun.second);
-
- item.set(std::string("[" AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK)
- + std::to_string(underrun.first->portId())
- + "]" AMEDIAMETRICS_PROP_UNDERRUN,
- (int32_t)underrun.second);
+ underrun.first->tallyUnderrunFrames(underrun.second);
}
- item.record();
}
}
@@ -4831,9 +4832,8 @@
private:
const mixer_state * const mMixerStatus;
- const std::string& mMetricsId;
std::vector<std::pair<sp<Track>, size_t>> mUnderrunFrames;
- } deferredOperations(&mixerStatus, mMetricsId);
+ } deferredOperations(&mixerStatus);
// implicit nested scope for variable capture
bool noFastHapticTrack = true;
@@ -5572,7 +5572,10 @@
status = mOutput->stream->setParameters(keyValuePair);
if (!mStandby && status == INVALID_OPERATION) {
mOutput->standby();
- mStandby = true;
+ if (!mStandby) {
+ mThreadMetrics.logEndInterval();
+ mStandby = true;
+ }
mBytesWritten = 0;
status = mOutput->stream->setParameters(keyValuePair);
}
@@ -6085,7 +6088,10 @@
status = mOutput->stream->setParameters(keyValuePair);
if (!mStandby && status == INVALID_OPERATION) {
mOutput->standby();
- mStandby = true;
+ if (!mStandby) {
+ mThreadMetrics.logEndInterval();
+ mStandby = true;
+ }
mBytesWritten = 0;
status = mOutput->stream->setParameters(keyValuePair);
}
@@ -6680,7 +6686,10 @@
// TODO: Report correction for the other output tracks and show in the dump.
}
- mStandby = false;
+ if (mStandby) {
+ mThreadMetrics.logBeginInterval();
+ mStandby = false;
+ }
return (ssize_t)mSinkBufferSize;
}
@@ -6842,7 +6851,7 @@
audio_io_handle_t id,
bool systemReady
) :
- ThreadBase(audioFlinger, id, RECORD, systemReady),
+ ThreadBase(audioFlinger, id, RECORD, systemReady, false /* isOut */),
mInput(input),
mSource(mInput),
mActiveTracks(&this->mLocalLog),
@@ -7130,7 +7139,10 @@
case TrackBase::STARTING_2:
doBroadcast = true;
- mStandby = false;
+ if (mStandby) {
+ mThreadMetrics.logBeginInterval();
+ mStandby = false;
+ }
activeTrack->mState = TrackBase::ACTIVE;
allStopped = false;
break;
@@ -7571,6 +7583,7 @@
{
if (!mStandby) {
inputStandBy();
+ mThreadMetrics.logEndInterval();
mStandby = true;
}
}
@@ -7875,6 +7888,9 @@
sendIoConfigEvent_l(
AUDIO_CLIENT_STARTED, recordTrack->creatorPid(), recordTrack->portId());
}
+
+ recordTrack->logBeginInterval(patchSourcesToString(&mPatch)); // log to MediaMetrics
+
// Catch up with current buffer indices if thread is already running.
// This is what makes a new client discard all buffered data. If the track's mRsmpInFront
// was initialized to some value closer to the thread's mRsmpInFront, then the track could
@@ -8539,12 +8555,15 @@
mPatch = *patch;
}
- mediametrics::LogItem(mMetricsId)
- .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CREATEAUDIOPATCH)
- .set(AMEDIAMETRICS_PROP_INPUTDEVICES, patchSourcesToString(patch).c_str())
- .set(AMEDIAMETRICS_PROP_SOURCE, toString(mAudioSource).c_str())
- .record();
-
+ const std::string pathSourcesAsString = patchSourcesToString(patch);
+ mThreadMetrics.logEndInterval();
+ mThreadMetrics.logCreatePatch(pathSourcesAsString);
+ mThreadMetrics.logBeginInterval();
+ // also dispatch to active AudioRecords
+ for (const auto &track : mActiveTracks) {
+ track->logEndInterval();
+ track->logBeginInterval(pathSourcesAsString);
+ }
return status;
}
@@ -8651,8 +8670,8 @@
AudioFlinger::MmapThread::MmapThread(
const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
- AudioHwDevice *hwDev, sp<StreamHalInterface> stream, bool systemReady)
- : ThreadBase(audioFlinger, id, MMAP, systemReady),
+ AudioHwDevice *hwDev, sp<StreamHalInterface> stream, bool systemReady, bool isOut)
+ : ThreadBase(audioFlinger, id, MMAP, systemReady, isOut),
mSessionId(AUDIO_SESSION_NONE),
mPortId(AUDIO_PORT_HANDLE_NONE),
mHalStream(stream), mHalDevice(hwDev->hwDevice()), mAudioHwDev(hwDev),
@@ -8735,7 +8754,10 @@
ALOGE("%s: error mHalStream->start() = %d for first track", __FUNCTION__, ret);
return ret;
}
- mStandby = false;
+ if (mStandby) {
+ mThreadMetrics.logBeginInterval();
+ mStandby = false;
+ }
return NO_ERROR;
}
@@ -8855,6 +8877,7 @@
chain->incActiveTrackCnt();
}
+ track->logBeginInterval(patchSinksToString(&mPatch)); // log to MediaMetrics
*handle = portId;
broadcast_l();
@@ -8923,7 +8946,10 @@
return INVALID_OPERATION;
}
mHalStream->standby();
- mStandby = true;
+ if (!mStandby) {
+ mThreadMetrics.logEndInterval();
+ mStandby = true;
+ }
releaseWakeLock();
return NO_ERROR;
}
@@ -8940,6 +8966,27 @@
result = mHalStream->getBufferSize(&mBufferSize);
LOG_ALWAYS_FATAL_IF(result != OK, "Error retrieving buffer size from HAL: %d", result);
mFrameCount = mBufferSize / mFrameSize;
+
+ // TODO: make a readHalParameters call?
+ mediametrics::LogItem item(mThreadMetrics.getMetricsId());
+ item.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_READPARAMETERS)
+ .set(AMEDIAMETRICS_PROP_ENCODING, formatToString(mFormat).c_str())
+ .set(AMEDIAMETRICS_PROP_SAMPLERATE, (int32_t)mSampleRate)
+ .set(AMEDIAMETRICS_PROP_CHANNELMASK, (int32_t)mChannelMask)
+ .set(AMEDIAMETRICS_PROP_CHANNELCOUNT, (int32_t)mChannelCount)
+ .set(AMEDIAMETRICS_PROP_FRAMECOUNT, (int32_t)mFrameCount)
+ /*
+ .set(AMEDIAMETRICS_PROP_FLAGS, toString(flags).c_str())
+ .set(AMEDIAMETRICS_PROP_PREFIX_HAPTIC AMEDIAMETRICS_PROP_CHANNELMASK,
+ (int32_t)mHapticChannelMask)
+ .set(AMEDIAMETRICS_PROP_PREFIX_HAPTIC AMEDIAMETRICS_PROP_CHANNELCOUNT,
+ (int32_t)mHapticChannelCount)
+ */
+ .set(AMEDIAMETRICS_PROP_PREFIX_HAL AMEDIAMETRICS_PROP_ENCODING,
+ formatToString(mHALFormat).c_str())
+ .set(AMEDIAMETRICS_PROP_PREFIX_HAL AMEDIAMETRICS_PROP_FRAMECOUNT,
+ (int32_t)mFrameCount) // sic - added HAL
+ .record();
}
bool AudioFlinger::MmapThread::threadLoop()
@@ -9152,6 +9199,7 @@
if (isOutput()) {
sendIoConfigEvent_l(AUDIO_OUTPUT_CONFIG_CHANGED);
mOutDeviceTypeAddrs = sinkDeviceTypeAddrs;
+ checkSilentMode_l();
} else {
sendIoConfigEvent_l(AUDIO_INPUT_CONFIG_CHANGED);
mInDeviceTypeAddr = sourceDeviceTypeAddr;
@@ -9350,7 +9398,7 @@
AudioFlinger::MmapPlaybackThread::MmapPlaybackThread(
const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
AudioHwDevice *hwDev, AudioStreamOut *output, bool systemReady)
- : MmapThread(audioFlinger, id, hwDev, output->stream, systemReady),
+ : MmapThread(audioFlinger, id, hwDev, output->stream, systemReady, true /* isOut */),
mStreamType(AUDIO_STREAM_MUSIC),
mStreamVolume(1.0),
mStreamMute(false),
@@ -9561,7 +9609,7 @@
AudioFlinger::MmapCaptureThread::MmapCaptureThread(
const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
AudioHwDevice *hwDev, AudioStreamIn *input, bool systemReady)
- : MmapThread(audioFlinger, id, hwDev, input->stream, systemReady),
+ : MmapThread(audioFlinger, id, hwDev, input->stream, systemReady, false /* isOut */),
mInput(input)
{
snprintf(mThreadName, kThreadNameLength, "AudioMmapIn_%X", id);
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index b56eabc..43746b9 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -37,7 +37,7 @@
static const char *threadTypeToString(type_t type);
ThreadBase(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
- type_t type, bool systemReady);
+ type_t type, bool systemReady, bool isOut);
virtual ~ThreadBase();
virtual status_t readyToRun();
@@ -330,7 +330,7 @@
return mInDeviceTypeAddr;
}
- virtual bool isOutput() const = 0;
+ bool isOutput() const { return mIsOut; }
virtual sp<StreamHalInterface> stream() const = 0;
@@ -524,7 +524,8 @@
Condition mWaitWorkCV;
const sp<AudioFlinger> mAudioFlinger;
- const std::string mMetricsId;
+ ThreadMetrics mThreadMetrics;
+ const bool mIsOut;
// updated by PlaybackThread::readOutputParameters_l() or
// RecordThread::readInputParameters_l()
@@ -911,9 +912,6 @@
// Return the asynchronous signal wait time.
virtual int64_t computeWaitTimeNs_l() const { return INT64_MAX; }
-
- virtual bool isOutput() const override { return true; }
-
// returns true if the track is allowed to be added to the thread.
virtual bool isTrackAllowed_l(
audio_channel_mask_t channelMask __unused,
@@ -1651,7 +1649,6 @@
ThreadBase::acquireWakeLock_l();
mActiveTracks.updatePowerState(this, true /* force */);
}
- virtual bool isOutput() const override { return false; }
void checkBtNrec();
@@ -1760,7 +1757,8 @@
#include "MmapTracks.h"
MmapThread(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
- AudioHwDevice *hwDev, sp<StreamHalInterface> stream, bool systemReady);
+ AudioHwDevice *hwDev, sp<StreamHalInterface> stream, bool systemReady,
+ bool isOut);
virtual ~MmapThread();
virtual void configure(const audio_attributes_t *attr,
@@ -1888,8 +1886,6 @@
virtual void checkSilentMode_l();
void processVolume_l() override;
- virtual bool isOutput() const override { return true; }
-
void updateMetadata_l() override;
virtual void toAudioPortConfig(struct audio_port_config *config);
@@ -1916,7 +1912,6 @@
AudioStreamIn* clearInput();
status_t exitStandby() override;
- virtual bool isOutput() const override { return false; }
void updateMetadata_l() override;
void processVolume_l() override;
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index e39b944..15c66fb 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -97,10 +97,7 @@
virtual void invalidate() {
if (mIsInvalid) return;
- mediametrics::LogItem(mMetricsId)
- .set(AMEDIAMETRICS_PROP_EVENT,
- AMEDIAMETRICS_PROP_EVENT_VALUE_INVALIDATE)
- .record();
+ mTrackMetrics.logInvalidate();
mIsInvalid = true;
}
bool isInvalid() const { return mIsInvalid; }
@@ -242,6 +239,20 @@
}
}
+ // Called by the PlaybackThread to indicate that the track is becoming active
+ // and a new interval should start with a given device list.
+ void logBeginInterval(const std::string& devices) {
+ mTrackMetrics.logBeginInterval(devices);
+ }
+
+ // Called by the PlaybackThread to indicate the track is no longer active.
+ void logEndInterval() {
+ mTrackMetrics.logEndInterval();
+ }
+
+ // Called to tally underrun frames in playback.
+ virtual void tallyUnderrunFrames(size_t /* frames */) {}
+
protected:
DISALLOW_COPY_AND_ASSIGN(TrackBase);
@@ -367,7 +378,7 @@
int64_t mLogStartTimeNs = 0;
int64_t mLogStartFrames = 0;
- const std::string mMetricsId;
+ TrackMetrics mTrackMetrics;
bool mServerLatencySupported = false;
std::atomic<bool> mServerLatencyFromTrack{}; // latency from track or server timestamp.
diff --git a/services/audioflinger/TrackMetrics.h b/services/audioflinger/TrackMetrics.h
new file mode 100644
index 0000000..399c788
--- /dev/null
+++ b/services/audioflinger/TrackMetrics.h
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2020 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 ANDROID_AUDIO_TRACKMETRICS_H
+#define ANDROID_AUDIO_TRACKMETRICS_H
+
+#include <mutex>
+
+namespace android {
+
+/**
+ * TrackMetrics handles the AudioFlinger track metrics.
+ *
+ * We aggregate metrics for a particular device for proper analysis.
+ * This includes power, performance, and usage metrics.
+ *
+ * This class is thread-safe with a lock for safety. There is no risk of deadlock
+ * as this class only executes external one-way calls in Mediametrics and does not
+ * call any other AudioFlinger class.
+ *
+ * Terminology:
+ * An AudioInterval is a contiguous playback segment.
+ * An AudioIntervalGroup is a group of continuous playback segments on the same device.
+ *
+ * We currently deliver metrics based on an AudioIntervalGroup.
+ */
+class TrackMetrics final {
+public:
+ TrackMetrics(std::string metricsId, bool isOut)
+ : mMetricsId(std::move(metricsId))
+ , mIsOut(isOut)
+ {} // we don't log a constructor item, we wait for more info in logConstructor().
+
+ ~TrackMetrics() {
+ logEndInterval();
+ std::lock_guard l(mLock);
+ deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP);
+ // we don't log a destructor item here.
+ }
+
+ // Called under the following circumstances
+ // 1) when we are added to the Thread
+ // 2) when we have a createPatch in the Thread.
+ void logBeginInterval(const std::string& devices) {
+ std::lock_guard l(mLock);
+ if (mDevices != devices) {
+ deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP);
+ mDevices = devices;
+ resetIntervalGroupMetrics();
+ deliverDeviceMetrics(
+ AMEDIAMETRICS_PROP_EVENT_VALUE_BEGINAUDIOINTERVALGROUP, devices.c_str());
+ }
+ ++mIntervalCount;
+ mIntervalStartTimeNs = systemTime();
+ }
+
+ void logConstructor(pid_t creatorPid, uid_t creatorUid) const {
+ // Once this item is logged by the server, the client can add properties.
+ // no lock required, all local or const variables.
+ mediametrics::LogItem(mMetricsId)
+ .setPid(creatorPid)
+ .setUid(creatorUid)
+ .set(AMEDIAMETRICS_PROP_ALLOWUID, (int32_t)creatorUid)
+ .set(AMEDIAMETRICS_PROP_EVENT,
+ AMEDIAMETRICS_PROP_PREFIX_SERVER AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
+ .record();
+ }
+
+ // 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;
+ }
+ }
+
+ void logInvalidate() const {
+ // no lock required, all local or const variables.
+ mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT,
+ AMEDIAMETRICS_PROP_EVENT_VALUE_INVALIDATE)
+ .record();
+ }
+
+ void logLatencyAndStartup(double latencyMs, double startupMs) {
+ mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_LATENCYMS, latencyMs)
+ .set(AMEDIAMETRICS_PROP_STARTUPMS, startupMs)
+ .record();
+ std::lock_guard l(mLock);
+ mDeviceLatencyMs.add(latencyMs);
+ mDeviceStartupMs.add(startupMs);
+ }
+
+ // 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;
+ return;
+ }
+ mDeviceVolume = (mDeviceVolume * (mLastVolumeChangeTimeNs - mStartVolumeTimeNs) +
+ mVolume * (timeNs - mLastVolumeChangeTimeNs)) / (timeNs - mStartVolumeTimeNs);
+ mVolume = volume;
+ mLastVolumeChangeTimeNs = timeNs;
+ }
+
+ // Use absolute numbers returned by AudioTrackShared.
+ void logUnderruns(size_t count, size_t frames) {
+ std::lock_guard l(mLock);
+ mUnderrunCount = count;
+ mUnderrunFrames = frames;
+ // Consider delivering a message here (also be aware of excessive spam).
+ }
+
+private:
+ // no lock required - all arguments and constants.
+ void deliverDeviceMetrics(const char *eventName, const char *devices) const {
+ mediametrics::LogItem(mMetricsId)
+ .set(AMEDIAMETRICS_PROP_EVENT, eventName)
+ .set(mIsOut ? AMEDIAMETRICS_PROP_OUTPUTDEVICES
+ : AMEDIAMETRICS_PROP_INPUTDEVICES, devices)
+ .record();
+ }
+
+ void deliverCumulativeMetrics(const char *eventName) const REQUIRES(mLock) {
+ if (mIntervalCount > 0) {
+ mediametrics::LogItem item(mMetricsId);
+ item.set(AMEDIAMETRICS_PROP_CUMULATIVETIMENS, mCumulativeTimeNs)
+ .set(AMEDIAMETRICS_PROP_DEVICETIMENS, mDeviceTimeNs)
+ .set(AMEDIAMETRICS_PROP_EVENT, eventName)
+ .set(AMEDIAMETRICS_PROP_INTERVALCOUNT, (int32_t)mIntervalCount);
+ if (mIsOut) {
+ item.set(AMEDIAMETRICS_PROP_DEVICEVOLUME, mDeviceVolume);
+ }
+ if (mDeviceLatencyMs.getN() > 0) {
+ item.set(AMEDIAMETRICS_PROP_DEVICELATENCYMS, mDeviceLatencyMs.getMean())
+ .set(AMEDIAMETRICS_PROP_DEVICESTARTUPMS, mDeviceStartupMs.getMean());
+ }
+ if (mUnderrunCount > 0) {
+ item.set(AMEDIAMETRICS_PROP_UNDERRUN,
+ (int32_t)(mUnderrunCount - mUnderrunCountSinceIntervalGroup))
+ .set(AMEDIAMETRICS_PROP_UNDERRUNFRAMES,
+ (int64_t)(mUnderrunFrames - mUnderrunFramesSinceIntervalGroup));
+ }
+ item.record();
+ }
+ }
+
+ void resetIntervalGroupMetrics() REQUIRES(mLock) {
+ // 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;
+
+ mDeviceLatencyMs.reset();
+ mDeviceStartupMs.reset();
+
+ mUnderrunCountSinceIntervalGroup = mUnderrunCount;
+ mUnderrunFramesSinceIntervalGroup = mUnderrunFrames;
+ // do not reset mUnderrunCount - it keeps continuously running for tracks.
+ }
+
+ const std::string mMetricsId;
+ const bool mIsOut; // if true, than a playback track, otherwise used for record.
+
+ mutable std::mutex mLock;
+
+ // Devices in the interval group.
+ std::string mDevices GUARDED_BY(mLock);
+
+ // 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;
+
+ // Average volume
+ double mVolume GUARDED_BY(mLock) = 0.f;
+ double mDeviceVolume GUARDED_BY(mLock) = 0.f;
+ int64_t mStartVolumeTimeNs GUARDED_BY(mLock) = 0;
+ int64_t mLastVolumeChangeTimeNs GUARDED_BY(mLock) = 0;
+
+ // latency and startup for each interval.
+ audio_utils::Statistics<double> mDeviceLatencyMs GUARDED_BY(mLock);
+ audio_utils::Statistics<double> mDeviceStartupMs GUARDED_BY(mLock);
+
+ // underrun count and frames
+ int64_t mUnderrunCount GUARDED_BY(mLock) = 0;
+ int64_t mUnderrunFrames GUARDED_BY(mLock) = 0;
+ int64_t mUnderrunCountSinceIntervalGroup GUARDED_BY(mLock) = 0;
+ int64_t mUnderrunFramesSinceIntervalGroup GUARDED_BY(mLock) = 0;
+};
+
+} // namespace android
+
+#endif // ANDROID_AUDIO_TRACKMETRICS_H
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 66a7b6f..2f29ed8 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -106,7 +106,7 @@
mThreadIoHandle(thread ? thread->id() : AUDIO_IO_HANDLE_NONE),
mPortId(portId),
mIsInvalid(false),
- mMetricsId(std::move(metricsId)),
+ mTrackMetrics(std::move(metricsId), isOut),
mCreatorPid(creatorPid)
{
const uid_t callingUid = IPCThreadState::self()->getCallingUid();
@@ -602,13 +602,7 @@
}
// Once this item is logged by the server, the client can add properties.
- mediametrics::LogItem(mMetricsId)
- .setPid(creatorPid)
- .setUid(uid)
- .set(AMEDIAMETRICS_PROP_ALLOWUID, (int32_t)uid)
- .set(AMEDIAMETRICS_PROP_EVENT,
- AMEDIAMETRICS_PROP_PREFIX_SERVER AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
- .record();
+ mTrackMetrics.logConstructor(creatorPid, uid);
}
AudioFlinger::PlaybackThread::Track::~Track()
@@ -1249,6 +1243,7 @@
if (mFinalVolume != volume) { // Compare to an epsilon if too many meaningless updates
mFinalVolume = volume;
setMetadataHasChanged();
+ mTrackMetrics.logVolume(volume);
}
}
@@ -1534,10 +1529,7 @@
(long long)mLogStartTimeNs,
(long long)local.mPosition[ExtendedTimestamp::LOCATION_SERVER],
(long long)mLogStartFrames);
- mediametrics::LogItem(mMetricsId)
- .set(AMEDIAMETRICS_PROP_LATENCYMS, latencyMs)
- .set(AMEDIAMETRICS_PROP_STARTUPMS, startUpMs)
- .record();
+ mTrackMetrics.logLatencyAndStartup(latencyMs, startUpMs);
}
}
}
@@ -2182,12 +2174,7 @@
#endif
// Once this item is logged by the server, the client can add properties.
- mediametrics::LogItem(mMetricsId)
- .setPid(creatorPid)
- .setUid(uid)
- .set(AMEDIAMETRICS_PROP_ALLOWUID, (int32_t)uid)
- .set(AMEDIAMETRICS_PROP_EVENT, "server." AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
- .record();
+ mTrackMetrics.logConstructor(creatorPid, uid);
}
AudioFlinger::RecordThread::RecordTrack::~RecordTrack()
@@ -2744,9 +2731,12 @@
nullptr /* buffer */, (size_t)0 /* bufferSize */,
sessionId, creatorPid, uid, isOut,
ALLOC_NONE,
- TYPE_DEFAULT, portId),
+ TYPE_DEFAULT, portId,
+ std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_MMAP) + std::to_string(portId)),
mPid(pid), mSilenced(false), mSilencedNotified(false)
{
+ // Once this item is logged by the server, the client can add properties.
+ mTrackMetrics.logConstructor(creatorPid, uid);
}
AudioFlinger::MmapThread::MmapTrack::~MmapTrack()
diff --git a/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp b/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp
index 886e4c9..d31e443 100644
--- a/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp
@@ -320,7 +320,7 @@
{
String8 devAddress = (address == nullptr || !matchAddress) ? String8("") : String8(address);
// handle legacy remote submix case where the address was not always specified
- if (device_distinguishes_on_address(deviceType) && (devAddress.length() == 0)) {
+ if (audio_is_remote_submix_device(deviceType) && (devAddress.length() == 0)) {
devAddress = String8("0");
}
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 5683e69..bcf6f38 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -954,7 +954,8 @@
primaryMix->mDeviceAddress,
AUDIO_FORMAT_DEFAULT);
sp<SwAudioOutputDescriptor> policyDesc = primaryMix->getOutput();
- if (policyDesc == nullptr || (policyDesc->mFlags & AUDIO_OUTPUT_FLAG_DIRECT)) {
+ if (deviceDesc != nullptr
+ && (policyDesc == nullptr || (policyDesc->mFlags & AUDIO_OUTPUT_FLAG_DIRECT))) {
audio_io_handle_t newOutput;
status = openDirectOutput(
*stream, session, config,
@@ -3072,7 +3073,7 @@
for (size_t i = 0; i < devices.size(); i++) {
sp<DeviceDescriptor> devDesc = mHwModules.getDeviceDescriptor(
devices[i].mType, devices[i].mAddress.c_str(), String8(),
- AUDIO_FORMAT_DEFAULT, false /*allowToCreate*/, false /*matchAddress*/);
+ AUDIO_FORMAT_DEFAULT, false /*allowToCreate*/, true /*matchAddress*/);
if (devDesc == nullptr || (predicate != nullptr && !predicate(devices[i].mType))) {
ALOGE("%s: device type %#x address %s not supported or not an output device",
context, devices[i].mType, devices[i].mAddress.c_str());
@@ -4837,7 +4838,7 @@
if (output != AUDIO_IO_HANDLE_NONE) {
addOutput(output, desc);
- if (device_distinguishes_on_address(deviceType) && address != "0") {
+ if (audio_is_remote_submix_device(deviceType) && address != "0") {
sp<AudioPolicyMix> policyMix;
if (mPolicyMixes.getAudioPolicyMix(deviceType, address, policyMix)
== NO_ERROR) {
diff --git a/services/audiopolicy/service/Android.bp b/services/audiopolicy/service/Android.bp
index 6ee573f..8a7a1b2 100644
--- a/services/audiopolicy/service/Android.bp
+++ b/services/audiopolicy/service/Android.bp
@@ -46,6 +46,7 @@
"-fvisibility=hidden",
"-Werror",
"-Wall",
+ "-Wthread-safety",
],
export_shared_lib_headers: [
diff --git a/services/audiopolicy/service/AudioPolicyEffects.cpp b/services/audiopolicy/service/AudioPolicyEffects.cpp
index 738a279..1ec0c5e 100644
--- a/services/audiopolicy/service/AudioPolicyEffects.cpp
+++ b/services/audiopolicy/service/AudioPolicyEffects.cpp
@@ -928,7 +928,10 @@
loadProcessingChain(result.parsedConfig->preprocess, mInputSources);
loadProcessingChain(result.parsedConfig->postprocess, mOutputStreams);
- loadDeviceProcessingChain(result.parsedConfig->deviceprocess, mDeviceEffects);
+ {
+ Mutex::Autolock _l(mLock);
+ loadDeviceProcessingChain(result.parsedConfig->deviceprocess, mDeviceEffects);
+ }
// Casting from ssize_t to status_t is probably safe, there should not be more than 2^31 errors
return result.nbSkippedElement;
}
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index d743be9..23de08b 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -58,8 +58,6 @@
AudioPolicyService::AudioPolicyService()
: BnAudioPolicyService(),
- mpAudioPolicyDev(NULL),
- mpAudioPolicy(NULL),
mAudioPolicyManager(NULL),
mAudioPolicyClient(NULL),
mPhoneState(AUDIO_MODE_INVALID),
@@ -78,21 +76,19 @@
mAudioPolicyClient = new AudioPolicyClient(this);
mAudioPolicyManager = createAudioPolicyManager(mAudioPolicyClient);
-
- mSupportedSystemUsages = std::vector<audio_usage_t> {};
}
// load audio processing modules
- sp<AudioPolicyEffects>audioPolicyEffects = new AudioPolicyEffects();
+ sp<AudioPolicyEffects> audioPolicyEffects = new AudioPolicyEffects();
+ sp<UidPolicy> uidPolicy = new UidPolicy(this);
+ sp<SensorPrivacyPolicy> sensorPrivacyPolicy = new SensorPrivacyPolicy(this);
{
Mutex::Autolock _l(mLock);
mAudioPolicyEffects = audioPolicyEffects;
+ mUidPolicy = uidPolicy;
+ mSensorPrivacyPolicy = sensorPrivacyPolicy;
}
-
- mUidPolicy = new UidPolicy(this);
- mUidPolicy->registerSelf();
-
- mSensorPrivacyPolicy = new SensorPrivacyPolicy(this);
- mSensorPrivacyPolicy->registerSelf();
+ uidPolicy->registerSelf();
+ sensorPrivacyPolicy->registerSelf();
}
AudioPolicyService::~AudioPolicyService()
@@ -107,9 +103,9 @@
mAudioPolicyEffects.clear();
mUidPolicy->unregisterSelf();
- mUidPolicy.clear();
-
mSensorPrivacyPolicy->unregisterSelf();
+
+ mUidPolicy.clear();
mSensorPrivacyPolicy.clear();
}
@@ -172,20 +168,20 @@
// removeNotificationClient() is called when the client process dies.
void AudioPolicyService::removeNotificationClient(uid_t uid, pid_t pid)
{
+ bool hasSameUid = false;
{
Mutex::Autolock _l(mNotificationClientsLock);
int64_t token = ((int64_t)uid<<32) | pid;
mNotificationClients.removeItem(token);
- }
- {
- Mutex::Autolock _l(mLock);
- bool hasSameUid = false;
for (size_t i = 0; i < mNotificationClients.size(); i++) {
if (mNotificationClients.valueAt(i)->uid() == uid) {
hasSameUid = true;
break;
}
}
+ }
+ {
+ Mutex::Autolock _l(mLock);
if (mAudioPolicyManager && !hasSameUid) {
// called from binder death notification: no need to clear caller identity
mAudioPolicyManager->releaseResourcesForUid(uid);
@@ -381,10 +377,14 @@
IPCThreadState::self()->getCallingPid());
}
-static bool dumpTryLock(Mutex& mutex)
+static bool dumpTryLock(Mutex& mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
{
- status_t err = mutex.timedLock(kDumpLockTimeoutNs);
- return err == NO_ERROR;
+ return mutex.timedLock(kDumpLockTimeoutNs) == NO_ERROR;
+}
+
+static void dumpReleaseLock(Mutex& mutex, bool locked) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
+{
+ if (locked) mutex.unlock();
}
status_t AudioPolicyService::dumpInternals(int fd)
@@ -564,7 +564,7 @@
bool isTopOrLatestSensitive = topSensitiveActive == nullptr ?
false : current->uid == topSensitiveActive->uid;
- auto canCaptureIfInCallOrCommunication = [&](const auto &recordClient) {
+ auto canCaptureIfInCallOrCommunication = [&](const auto &recordClient) REQUIRES(mLock) {
bool canCaptureCall = recordClient->canCaptureOutput;
bool canCaptureCommunication = recordClient->canCaptureOutput
|| recordClient->uid == mPhoneStateOwnerUid
@@ -676,6 +676,7 @@
case AUDIO_SOURCE_VOICE_CALL:
case AUDIO_SOURCE_REMOTE_SUBMIX:
case AUDIO_SOURCE_FM_TUNER:
+ case AUDIO_SOURCE_ECHO_REFERENCE:
return true;
default:
break;
@@ -702,7 +703,7 @@
if (!dumpAllowed()) {
dumpPermissionDenial(fd);
} else {
- bool locked = dumpTryLock(mLock);
+ const bool locked = dumpTryLock(mLock);
if (!locked) {
String8 result(kDeadlockedString);
write(fd, result.string(), result.size());
@@ -719,7 +720,7 @@
mPackageManager.dump(fd);
- if (locked) mLock.unlock();
+ dumpReleaseLock(mLock, locked);
}
return NO_ERROR;
}
@@ -838,8 +839,16 @@
return BAD_VALUE;
}
- mUidPolicy->addOverrideUid(uid, active);
- return NO_ERROR;
+ sp<UidPolicy> uidPolicy;
+ {
+ Mutex::Autolock _l(mLock);
+ uidPolicy = mUidPolicy;
+ }
+ if (uidPolicy) {
+ uidPolicy->addOverrideUid(uid, active);
+ return NO_ERROR;
+ }
+ return NO_INIT;
}
status_t AudioPolicyService::handleResetUidState(Vector<String16>& args, int err) {
@@ -859,8 +868,16 @@
return BAD_VALUE;
}
- mUidPolicy->removeOverrideUid(uid);
- return NO_ERROR;
+ sp<UidPolicy> uidPolicy;
+ {
+ Mutex::Autolock _l(mLock);
+ uidPolicy = mUidPolicy;
+ }
+ if (uidPolicy) {
+ uidPolicy->removeOverrideUid(uid);
+ return NO_ERROR;
+ }
+ return NO_INIT;
}
status_t AudioPolicyService::handleGetUidState(Vector<String16>& args, int out, int err) {
@@ -880,11 +897,15 @@
return BAD_VALUE;
}
- if (mUidPolicy->isUidActive(uid)) {
- return dprintf(out, "active\n");
- } else {
- return dprintf(out, "idle\n");
+ sp<UidPolicy> uidPolicy;
+ {
+ Mutex::Autolock _l(mLock);
+ uidPolicy = mUidPolicy;
}
+ if (uidPolicy) {
+ return dprintf(out, uidPolicy->isUidActive(uid) ? "active\n" : "idle\n");
+ }
+ return NO_INIT;
}
status_t AudioPolicyService::printHelp(int out) {
@@ -1401,7 +1422,7 @@
result.append(buffer);
write(fd, result.string(), result.size());
- bool locked = dumpTryLock(mLock);
+ const bool locked = dumpTryLock(mLock);
if (!locked) {
String8 result2(kCmdDeadlockedString);
write(fd, result2.string(), result2.size());
@@ -1424,7 +1445,7 @@
write(fd, result.string(), result.size());
- if (locked) mLock.unlock();
+ dumpReleaseLock(mLock, locked);
return NO_ERROR;
}
diff --git a/services/audiopolicy/service/AudioPolicyService.h b/services/audiopolicy/service/AudioPolicyService.h
index f77a481..869a963 100644
--- a/services/audiopolicy/service/AudioPolicyService.h
+++ b/services/audiopolicy/service/AudioPolicyService.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_AUDIOPOLICYSERVICE_H
#define ANDROID_AUDIOPOLICYSERVICE_H
+#include <android-base/thread_annotations.h>
#include <cutils/misc.h>
#include <cutils/config_utils.h>
#include <cutils/compiler.h>
@@ -330,13 +331,13 @@
AudioPolicyService() ANDROID_API;
virtual ~AudioPolicyService();
- status_t dumpInternals(int fd);
+ status_t dumpInternals(int fd) REQUIRES(mLock);
// Handles binder shell commands
virtual status_t shellCommand(int in, int out, int err, Vector<String16>& args);
// Sets whether the given UID records only silence
- virtual void setAppState_l(audio_port_handle_t portId, app_state_t state);
+ virtual void setAppState_l(audio_port_handle_t portId, app_state_t state) REQUIRES(mLock);
// Overrides the UID state as if it is idle
status_t handleSetUidState(Vector<String16>& args, int err);
@@ -361,9 +362,9 @@
status_t validateUsage(audio_usage_t usage, pid_t pid, uid_t uid);
void updateUidStates();
- void updateUidStates_l();
+ void updateUidStates_l() REQUIRES(mLock);
- void silenceAllRecordings_l();
+ void silenceAllRecordings_l() REQUIRES(mLock);
static bool isVirtualSource(audio_source_t source);
@@ -420,13 +421,13 @@
wp<AudioPolicyService> mService;
Mutex mLock;
ActivityManager mAm;
- bool mObserverRegistered;
+ bool mObserverRegistered = false;
std::unordered_map<uid_t, std::pair<bool, int>> mOverrideUids;
std::unordered_map<uid_t, std::pair<bool, int>> mCachedUids;
- uid_t mAssistantUid;
+ uid_t mAssistantUid = -1;
std::vector<uid_t> mA11yUids;
- uid_t mCurrentImeUid;
- bool mRttEnabled;
+ uid_t mCurrentImeUid = -1;
+ bool mRttEnabled = false;
};
// If sensor privacy is enabled then all apps, including those that are active, should be
@@ -447,7 +448,7 @@
private:
wp<AudioPolicyService> mService;
- std::atomic_bool mSensorPrivacyEnabled;
+ std::atomic_bool mSensorPrivacyEnabled = false;
};
// Thread used to send audio config commands to audio flinger
@@ -880,26 +881,27 @@
// and possibly back in to audio policy service and acquire mEffectsLock.
sp<AudioCommandThread> mAudioCommandThread; // audio commands thread
sp<AudioCommandThread> mOutputCommandThread; // process stop and release output
- struct audio_policy_device *mpAudioPolicyDev;
- struct audio_policy *mpAudioPolicy;
AudioPolicyInterface *mAudioPolicyManager;
AudioPolicyClient *mAudioPolicyClient;
std::vector<audio_usage_t> mSupportedSystemUsages;
- DefaultKeyedVector< int64_t, sp<NotificationClient> > mNotificationClients;
- Mutex mNotificationClientsLock; // protects mNotificationClients
+ Mutex mNotificationClientsLock;
+ DefaultKeyedVector<int64_t, sp<NotificationClient>> mNotificationClients
+ GUARDED_BY(mNotificationClientsLock);
// Manage all effects configured in audio_effects.conf
// never hold AudioPolicyService::mLock when calling AudioPolicyEffects methods as
// those can call back into AudioPolicyService methods and try to acquire the mutex
- sp<AudioPolicyEffects> mAudioPolicyEffects;
- audio_mode_t mPhoneState;
- uid_t mPhoneStateOwnerUid;
+ sp<AudioPolicyEffects> mAudioPolicyEffects GUARDED_BY(mLock);
+ audio_mode_t mPhoneState GUARDED_BY(mLock);
+ uid_t mPhoneStateOwnerUid GUARDED_BY(mLock);
- sp<UidPolicy> mUidPolicy;
- sp<SensorPrivacyPolicy> mSensorPrivacyPolicy;
+ sp<UidPolicy> mUidPolicy GUARDED_BY(mLock);
+ sp<SensorPrivacyPolicy> mSensorPrivacyPolicy GUARDED_BY(mLock);
- DefaultKeyedVector< audio_port_handle_t, sp<AudioRecordClient> > mAudioRecordClients;
- DefaultKeyedVector< audio_port_handle_t, sp<AudioPlaybackClient> > mAudioPlaybackClients;
+ DefaultKeyedVector<audio_port_handle_t, sp<AudioRecordClient>> mAudioRecordClients
+ GUARDED_BY(mLock);
+ DefaultKeyedVector<audio_port_handle_t, sp<AudioPlaybackClient>> mAudioPlaybackClients
+ GUARDED_BY(mLock);
MediaPackageManager mPackageManager; // To check allowPlaybackCapture
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 2d46122..b00a2d9 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -2344,6 +2344,21 @@
}
}
+bool Camera3Device::checkAbandonedStreamsLocked() {
+ if ((mInputStream.get() != nullptr) && (mInputStream->isAbandoned())) {
+ return true;
+ }
+
+ for (size_t i = 0; i < mOutputStreams.size(); i++) {
+ auto stream = mOutputStreams[i];
+ if ((stream.get() != nullptr) && (stream->isAbandoned())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
bool Camera3Device::reconfigureCamera(const CameraMetadata& sessionParams) {
ATRACE_CALL();
bool ret = false;
@@ -2352,6 +2367,12 @@
nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
Mutex::Autolock l(mLock);
+ if (checkAbandonedStreamsLocked()) {
+ ALOGW("%s: Abandoned stream detected, session parameters can't be applied correctly!",
+ __FUNCTION__);
+ return true;
+ }
+
auto rc = internalPauseAndWaitLocked(maxExpectedDuration);
if (rc == NO_ERROR) {
mNeedConfig = true;
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index b373a64..19ecf4b 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -648,6 +648,12 @@
bool reconfigureCamera(const CameraMetadata& sessionParams);
/**
+ * Return true in case of any output or input abandoned streams,
+ * otherwise return false.
+ */
+ bool checkAbandonedStreamsLocked();
+
+ /**
* Filter stream session parameters and configure camera HAL.
*/
status_t filterParamsAndConfigureLocked(const CameraMetadata& sessionParams,
diff --git a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
index 4c8366f..603f516 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
@@ -259,13 +259,15 @@
}
// Fix up some result metadata to account for HAL-level distortion correction
- status_t res =
- states.distortionMappers[states.cameraId.c_str()].correctCaptureResult(
- &captureResult.mMetadata);
- if (res != OK) {
- SET_ERR("Unable to correct capture result metadata for frame %d: %s (%d)",
- frameNumber, strerror(-res), res);
- return;
+ status_t res = OK;
+ auto iter = states.distortionMappers.find(states.cameraId.c_str());
+ if (iter != states.distortionMappers.end()) {
+ res = iter->second.correctCaptureResult(&captureResult.mMetadata);
+ if (res != OK) {
+ SET_ERR("Unable to correct capture result metadata for frame %d: %s (%d)",
+ frameNumber, strerror(-res), res);
+ return;
+ }
}
// Fix up result metadata to account for zoom ratio availabilities between
diff --git a/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp b/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp
index 84da45a..a87de77 100644
--- a/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp
+++ b/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp
@@ -243,10 +243,15 @@
if (weight == 0) {
continue;
}
- // Top-left is inclusively clamped
- scaleCoordinates(entry.data.i32 + j, 1, zoomRatio, ClampInclusive);
- // Bottom-right is exclusively clamped
- scaleCoordinates(entry.data.i32 + j + 2, 1, zoomRatio, ClampExclusive);
+ // Top left (inclusive)
+ scaleCoordinates(entry.data.i32 + j, 1, zoomRatio, true /*clamp*/);
+ // Bottom right (exclusive): Use adjacent inclusive pixel to
+ // calculate.
+ entry.data.i32[j+2] -= 1;
+ entry.data.i32[j+3] -= 1;
+ scaleCoordinates(entry.data.i32 + j + 2, 1, zoomRatio, true /*clamp*/);
+ entry.data.i32[j+2] += 1;
+ entry.data.i32[j+3] += 1;
}
}
@@ -258,7 +263,7 @@
if (isResult) {
for (auto pts : kResultPointsToCorrectNoClamp) {
entry = metadata->find(pts);
- scaleCoordinates(entry.data.i32, entry.count / 2, zoomRatio, ClampOff);
+ scaleCoordinates(entry.data.i32, entry.count / 2, zoomRatio, false /*clamp*/);
}
}
@@ -282,10 +287,15 @@
if (weight == 0) {
continue;
}
- // Top-left is inclusively clamped
- scaleCoordinates(entry.data.i32 + j, 1, 1.0 / zoomRatio, ClampInclusive);
- // Bottom-right is exclusively clamped
- scaleCoordinates(entry.data.i32 + j + 2, 1, 1.0 / zoomRatio, ClampExclusive);
+ // Top-left (inclusive)
+ scaleCoordinates(entry.data.i32 + j, 1, 1.0 / zoomRatio, true /*clamp*/);
+ // Bottom-right (exclusive): Use adjacent inclusive pixel to
+ // calculate.
+ entry.data.i32[j+2] -= 1;
+ entry.data.i32[j+3] -= 1;
+ scaleCoordinates(entry.data.i32 + j + 2, 1, 1.0 / zoomRatio, true /*clamp*/);
+ entry.data.i32[j+2] += 1;
+ entry.data.i32[j+3] += 1;
}
}
for (auto rect : kRectsToCorrect) {
@@ -295,7 +305,7 @@
if (isResult) {
for (auto pts : kResultPointsToCorrectNoClamp) {
entry = metadata->find(pts);
- scaleCoordinates(entry.data.i32, entry.count / 2, 1.0 / zoomRatio, ClampOff);
+ scaleCoordinates(entry.data.i32, entry.count / 2, 1.0 / zoomRatio, false /*clamp*/);
}
}
@@ -309,28 +319,31 @@
}
void ZoomRatioMapper::scaleCoordinates(int32_t* coordPairs, int coordCount,
- float scaleRatio, ClampMode clamp) {
+ float scaleRatio, bool clamp) {
+ // A pixel's coordinate is represented by the position of its top-left corner.
+ // To avoid the rounding error, we use the coordinate for the center of the
+ // pixel instead:
+ // 1. First shift the coordinate system half pixel both horizontally and
+ // vertically, so that [x, y] is the center of the pixel, not the top-left corner.
+ // 2. Do zoom operation to scale the coordinate relative to the center of
+ // the active array (shifted by 0.5 pixel as well).
+ // 3. Shift the coordinate system back by directly using the pixel center
+ // coordinate.
for (int i = 0; i < coordCount * 2; i += 2) {
float x = coordPairs[i];
float y = coordPairs[i + 1];
- float xCentered = x - mArrayWidth / 2;
- float yCentered = y - mArrayHeight / 2;
+ float xCentered = x - (mArrayWidth - 2) / 2;
+ float yCentered = y - (mArrayHeight - 2) / 2;
float scaledX = xCentered * scaleRatio;
float scaledY = yCentered * scaleRatio;
- scaledX += mArrayWidth / 2;
- scaledY += mArrayHeight / 2;
+ scaledX += (mArrayWidth - 2) / 2;
+ scaledY += (mArrayHeight - 2) / 2;
+ coordPairs[i] = static_cast<int32_t>(std::round(scaledX));
+ coordPairs[i+1] = static_cast<int32_t>(std::round(scaledY));
// Clamp to within activeArray/preCorrectionActiveArray
- coordPairs[i] = static_cast<int32_t>(scaledX);
- coordPairs[i+1] = static_cast<int32_t>(scaledY);
- if (clamp != ClampOff) {
- int32_t right, bottom;
- if (clamp == ClampInclusive) {
- right = mArrayWidth - 1;
- bottom = mArrayHeight - 1;
- } else {
- right = mArrayWidth;
- bottom = mArrayHeight;
- }
+ if (clamp) {
+ int32_t right = mArrayWidth - 1;
+ int32_t bottom = mArrayHeight - 1;
coordPairs[i] =
std::min(right, std::max(0, coordPairs[i]));
coordPairs[i+1] =
@@ -343,25 +356,25 @@
void ZoomRatioMapper::scaleRects(int32_t* rects, int rectCount,
float scaleRatio) {
for (int i = 0; i < rectCount * 4; i += 4) {
- // Map from (l, t, width, height) to (l, t, r, b).
- // [l, t] is inclusive, and [r, b] is exclusive.
+ // Map from (l, t, width, height) to (l, t, l+width-1, t+height-1),
+ // where both top-left and bottom-right are inclusive.
int32_t coords[4] = {
rects[i],
rects[i + 1],
- rects[i] + rects[i + 2],
- rects[i + 1] + rects[i + 3]
+ rects[i] + rects[i + 2] - 1,
+ rects[i + 1] + rects[i + 3] - 1
};
// top-left
- scaleCoordinates(coords, 1, scaleRatio, ClampInclusive);
+ scaleCoordinates(coords, 1, scaleRatio, true /*clamp*/);
// bottom-right
- scaleCoordinates(coords+2, 1, scaleRatio, ClampExclusive);
+ scaleCoordinates(coords+2, 1, scaleRatio, true /*clamp*/);
// Map back to (l, t, width, height)
rects[i] = coords[0];
rects[i + 1] = coords[1];
- rects[i + 2] = coords[2] - coords[0];
- rects[i + 3] = coords[3] - coords[1];
+ rects[i + 2] = coords[2] - coords[0] + 1;
+ rects[i + 3] = coords[3] - coords[1] + 1;
}
}
diff --git a/services/camera/libcameraservice/device3/ZoomRatioMapper.h b/services/camera/libcameraservice/device3/ZoomRatioMapper.h
index aa3d913..698f87f 100644
--- a/services/camera/libcameraservice/device3/ZoomRatioMapper.h
+++ b/services/camera/libcameraservice/device3/ZoomRatioMapper.h
@@ -65,14 +65,8 @@
status_t updateCaptureResult(CameraMetadata *request, bool requestedZoomRatioIs1);
public: // Visible for testing. Do not use concurently.
- enum ClampMode {
- ClampOff,
- ClampInclusive,
- ClampExclusive,
- };
-
void scaleCoordinates(int32_t* coordPairs, int coordCount,
- float scaleRatio, ClampMode clamp);
+ float scaleRatio, bool clamp);
bool isValid() { return mIsValid; }
private:
diff --git a/services/camera/libcameraservice/tests/ZoomRatioTest.cpp b/services/camera/libcameraservice/tests/ZoomRatioTest.cpp
index 300da09..4e94991 100644
--- a/services/camera/libcameraservice/tests/ZoomRatioTest.cpp
+++ b/services/camera/libcameraservice/tests/ZoomRatioTest.cpp
@@ -171,35 +171,35 @@
std::array<int32_t, 16> originalCoords = {
0, 0, // top-left
- width, 0, // top-right
- 0, height, // bottom-left
- width, height, // bottom-right
- width / 2, height / 2, // center
- width / 4, height / 4, // top-left after 2x
- width / 3, height * 2 / 3, // bottom-left after 3x zoom
- width * 7 / 8, height / 2, // middle-right after 1.33x zoom
+ width - 1, 0, // top-right
+ 0, height - 1, // bottom-left
+ width - 1, height - 1, // bottom-right
+ (width - 1) / 2, (height - 1) / 2, // center
+ (width - 1) / 4, (height - 1) / 4, // top-left after 2x
+ (width - 1) / 3, (height - 1) * 2 / 3, // bottom-left after 3x zoom
+ (width - 1) * 7 / 8, (height - 1) / 2, // middle-right after 1.33x zoom
};
// Verify 1.0x zoom doesn't change the coordinates
auto coords = originalCoords;
- mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f, ZoomRatioMapper::ClampOff);
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f, false /*clamp*/);
for (size_t i = 0; i < coords.size(); i++) {
EXPECT_EQ(coords[i], originalCoords[i]);
}
// Verify 2.0x zoom work as expected (no clamping)
std::array<float, 16> expected2xCoords = {
- - width / 2.0f, - height / 2.0f,// top-left
- width * 3 / 2.0f, - height / 2.0f, // top-right
- - width / 2.0f, height * 3 / 2.0f, // bottom-left
- width * 3 / 2.0f, height * 3 / 2.0f, // bottom-right
- width / 2.0f, height / 2.0f, // center
+ - (width - 1) / 2.0f, - (height - 1) / 2.0f,// top-left
+ (width - 1) * 3 / 2.0f, - (height - 1) / 2.0f, // top-right
+ - (width - 1) / 2.0f, (height - 1) * 3 / 2.0f, // bottom-left
+ (width - 1) * 3 / 2.0f, (height - 1) * 3 / 2.0f, // bottom-right
+ (width - 1) / 2.0f, (height - 1) / 2.0f, // center
0, 0, // top-left after 2x
- width / 6.0f, height - height / 6.0f, // bottom-left after 3x zoom
- width + width / 4.0f, height / 2.0f, // middle-right after 1.33x zoom
+ (width - 1) / 6.0f, (height - 1) * 5.0f / 6.0f, // bottom-left after 3x zoom
+ (width - 1) * 5.0f / 4.0f, (height - 1) / 2.0f, // middle-right after 1.33x zoom
};
coords = originalCoords;
- mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampOff);
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, false /*clamp*/);
for (size_t i = 0; i < coords.size(); i++) {
EXPECT_LE(std::abs(coords[i] - expected2xCoords[i]), kMaxAllowedPixelError);
}
@@ -207,16 +207,16 @@
// Verify 2.0x zoom work as expected (with inclusive clamping)
std::array<float, 16> expected2xCoordsClampedInc = {
0, 0, // top-left
- static_cast<float>(width) - 1, 0, // top-right
- 0, static_cast<float>(height) - 1, // bottom-left
- static_cast<float>(width) - 1, static_cast<float>(height) - 1, // bottom-right
- width / 2.0f, height / 2.0f, // center
+ width - 1.0f, 0, // top-right
+ 0, height - 1.0f, // bottom-left
+ width - 1.0f, height - 1.0f, // bottom-right
+ (width - 1) / 2.0f, (height - 1) / 2.0f, // center
0, 0, // top-left after 2x
- width / 6.0f, height - height / 6.0f , // bottom-left after 3x zoom
- static_cast<float>(width) - 1, height / 2.0f, // middle-right after 1.33x zoom
+ (width - 1) / 6.0f, (height - 1) * 5.0f / 6.0f , // bottom-left after 3x zoom
+ width - 1.0f, (height - 1) / 2.0f, // middle-right after 1.33x zoom
};
coords = originalCoords;
- mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampInclusive);
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, true /*clamp*/);
for (size_t i = 0; i < coords.size(); i++) {
EXPECT_LE(std::abs(coords[i] - expected2xCoordsClampedInc[i]), kMaxAllowedPixelError);
}
@@ -224,33 +224,33 @@
// Verify 2.0x zoom work as expected (with exclusive clamping)
std::array<float, 16> expected2xCoordsClampedExc = {
0, 0, // top-left
- static_cast<float>(width), 0, // top-right
- 0, static_cast<float>(height), // bottom-left
- static_cast<float>(width), static_cast<float>(height), // bottom-right
+ width - 1.0f, 0, // top-right
+ 0, height - 1.0f, // bottom-left
+ width - 1.0f, height - 1.0f, // bottom-right
width / 2.0f, height / 2.0f, // center
0, 0, // top-left after 2x
- width / 6.0f, height - height / 6.0f , // bottom-left after 3x zoom
- static_cast<float>(width), height / 2.0f, // middle-right after 1.33x zoom
+ (width - 1) / 6.0f, (height - 1) * 5.0f / 6.0f , // bottom-left after 3x zoom
+ width - 1.0f, height / 2.0f, // middle-right after 1.33x zoom
};
coords = originalCoords;
- mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampExclusive);
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, true /*clamp*/);
for (size_t i = 0; i < coords.size(); i++) {
EXPECT_LE(std::abs(coords[i] - expected2xCoordsClampedExc[i]), kMaxAllowedPixelError);
}
// Verify 0.33x zoom work as expected
std::array<float, 16> expectedZoomOutCoords = {
- width / 3.0f, height / 3.0f, // top-left
- width * 2 / 3.0f, height / 3.0f, // top-right
- width / 3.0f, height * 2 / 3.0f, // bottom-left
- width * 2 / 3.0f, height * 2 / 3.0f, // bottom-right
- width / 2.0f, height / 2.0f, // center
- width * 5 / 12.0f, height * 5 / 12.0f, // top-left after 2x
- width * 4 / 9.0f, height * 5 / 9.0f, // bottom-left after 3x zoom-in
- width * 5 / 8.0f, height / 2.0f, // middle-right after 1.33x zoom-in
+ (width - 1) / 3.0f, (height - 1) / 3.0f, // top-left
+ (width - 1) * 2 / 3.0f, (height - 1) / 3.0f, // top-right
+ (width - 1) / 3.0f, (height - 1) * 2 / 3.0f, // bottom-left
+ (width - 1) * 2 / 3.0f, (height - 1) * 2 / 3.0f, // bottom-right
+ (width - 1) / 2.0f, (height - 1) / 2.0f, // center
+ (width - 1) * 5 / 12.0f, (height - 1) * 5 / 12.0f, // top-left after 2x
+ (width - 1) * 4 / 9.0f, (height - 1) * 5 / 9.0f, // bottom-left after 3x zoom-in
+ (width - 1) * 5 / 8.0f, (height - 1) / 2.0f, // middle-right after 1.33x zoom-in
};
coords = originalCoords;
- mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f/3, ZoomRatioMapper::ClampOff);
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f/3, false /*clamp*/);
for (size_t i = 0; i < coords.size(); i++) {
EXPECT_LE(std::abs(coords[i] - expectedZoomOutCoords[i]), kMaxAllowedPixelError);
}
@@ -323,7 +323,8 @@
entry = metadata.find(ANDROID_SCALER_CROP_REGION);
ASSERT_EQ(entry.count, 4U);
for (int i = 0; i < 4; i++) {
- EXPECT_EQ(entry.data.i32[i], testDefaultCropSize[index][i]);
+ EXPECT_LE(std::abs(entry.data.i32[i] - testDefaultCropSize[index][i]),
+ kMaxAllowedPixelError);
}
entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
EXPECT_NEAR(entry.data.f[0], 2.0f, kMaxAllowedRatioError);
@@ -335,7 +336,7 @@
entry = metadata.find(ANDROID_SCALER_CROP_REGION);
ASSERT_EQ(entry.count, 4U);
for (int i = 0; i < 4; i++) {
- EXPECT_EQ(entry.data.i32[i], test2xCropRegion[index][i]);
+ EXPECT_LE(std::abs(entry.data.i32[i] - test2xCropRegion[index][i]), kMaxAllowedPixelError);
}
// Letter boxing crop region, zoomRatio is 1.0
diff --git a/services/mediametrics/mediametrics.rc b/services/mediametrics/mediametrics.rc
index 1efde5e..2a6c817 100644
--- a/services/mediametrics/mediametrics.rc
+++ b/services/mediametrics/mediametrics.rc
@@ -3,4 +3,4 @@
user media
group media
ioprio rt 4
- writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+ task_profiles ProcessCapacityHigh HighPerformance
diff --git a/services/mediaresourcemanager/ResourceManagerService.cpp b/services/mediaresourcemanager/ResourceManagerService.cpp
index be5af00..ff45c87 100644
--- a/services/mediaresourcemanager/ResourceManagerService.cpp
+++ b/services/mediaresourcemanager/ResourceManagerService.cpp
@@ -114,6 +114,7 @@
info.uid = uid;
info.clientId = clientId;
info.client = client;
+ info.pendingRemoval = false;
index = infos.add(clientId, info);
}
@@ -648,6 +649,36 @@
return Status::ok();
}
+Status ResourceManagerService::markClientForPendingRemoval(int32_t pid, int64_t clientId) {
+ String8 log = String8::format(
+ "markClientForPendingRemoval(pid %d, clientId %lld)",
+ pid, (long long) clientId);
+ mServiceLog->add(log);
+
+ Mutex::Autolock lock(mLock);
+ if (!mProcessInfo->isValidPid(pid)) {
+ ALOGE("Rejected markClientForPendingRemoval call with invalid pid.");
+ return Status::fromServiceSpecificError(BAD_VALUE);
+ }
+ ssize_t index = mMap.indexOfKey(pid);
+ if (index < 0) {
+ ALOGV("markClientForPendingRemoval: didn't find pid %d for clientId %lld",
+ pid, (long long)clientId);
+ return Status::ok();
+ }
+ ResourceInfos &infos = mMap.editValueAt(index);
+
+ index = infos.indexOfKey(clientId);
+ if (index < 0) {
+ ALOGV("markClientForPendingRemoval: didn't find clientId %lld", (long long) clientId);
+ return Status::ok();
+ }
+
+ ResourceInfo &info = infos.editValueAt(index);
+ info.pendingRemoval = true;
+ return Status::ok();
+}
+
bool ResourceManagerService::getPriority_l(int pid, int* priority) {
int newPid = pid;
@@ -693,6 +724,12 @@
int lowestPriorityPid;
int lowestPriority;
int callingPriority;
+
+ // Before looking into other processes, check if we have clients marked for
+ // pending removal in the same process.
+ if (getBiggestClient_l(callingPid, type, client, true /* pendingRemovalOnly */)) {
+ return true;
+ }
if (!getPriority_l(callingPid, &callingPriority)) {
ALOGE("getLowestPriorityBiggestClient_l: can't get process priority for pid %d",
callingPid);
@@ -761,7 +798,8 @@
}
bool ResourceManagerService::getBiggestClient_l(
- int pid, MediaResource::Type type, std::shared_ptr<IResourceManagerClient> *client) {
+ int pid, MediaResource::Type type, std::shared_ptr<IResourceManagerClient> *client,
+ bool pendingRemovalOnly) {
ssize_t index = mMap.indexOfKey(pid);
if (index < 0) {
ALOGE("getBiggestClient_l: can't find resource info for pid %d", pid);
@@ -773,6 +811,9 @@
const ResourceInfos &infos = mMap.valueAt(index);
for (size_t i = 0; i < infos.size(); ++i) {
const ResourceList &resources = infos[i].resources;
+ if (pendingRemovalOnly && !infos[i].pendingRemoval) {
+ continue;
+ }
for (auto it = resources.begin(); it != resources.end(); it++) {
const MediaResourceParcel &resource = it->second;
if (resource.type == type) {
diff --git a/services/mediaresourcemanager/ResourceManagerService.h b/services/mediaresourcemanager/ResourceManagerService.h
index 92048c3..49c247e 100644
--- a/services/mediaresourcemanager/ResourceManagerService.h
+++ b/services/mediaresourcemanager/ResourceManagerService.h
@@ -18,6 +18,8 @@
#ifndef ANDROID_MEDIA_RESOURCEMANAGERSERVICE_H
#define ANDROID_MEDIA_RESOURCEMANAGERSERVICE_H
+#include <map>
+
#include <aidl/android/media/BnResourceManagerService.h>
#include <arpa/inet.h>
#include <media/MediaResource.h>
@@ -50,6 +52,7 @@
std::shared_ptr<IResourceManagerClient> client;
sp<DeathNotifier> deathNotifier;
ResourceList resources;
+ bool pendingRemoval{false};
};
// TODO: convert these to std::map
@@ -122,6 +125,8 @@
int originalPid,
int newPid) override;
+ Status markClientForPendingRemoval(int32_t pid, int64_t clientId) override;
+
Status removeResource(int pid, int64_t clientId, bool checkValid);
private:
@@ -146,7 +151,8 @@
// Gets the client who owns biggest piece of specified resource type from pid.
// Returns false if failed. The client will remain unchanged if failed.
bool getBiggestClient_l(int pid, MediaResource::Type type,
- std::shared_ptr<IResourceManagerClient> *client);
+ std::shared_ptr<IResourceManagerClient> *client,
+ bool pendingRemovalOnly = false);
bool isCallingPriorityHigher_l(int callingPid, int pid);
diff --git a/services/mediaresourcemanager/test/ResourceManagerService_test.cpp b/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
index 5d839fa..702935d 100644
--- a/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
+++ b/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
@@ -472,6 +472,56 @@
}
}
+ void testMarkClientForPendingRemoval() {
+ bool result;
+
+ {
+ addResource();
+ mService->mSupportsSecureWithNonSecureCodec = true;
+
+ std::vector<MediaResourceParcel> resources;
+ resources.push_back(MediaResource(MediaResource::Type::kNonSecureCodec, 1));
+
+ // Remove low priority clients
+ mService->removeClient(kTestPid1, getId(mTestClient1));
+
+ // no lower priority client
+ CHECK_STATUS_FALSE(mService->reclaimResource(kTestPid2, resources, &result));
+ verifyClients(false /* c1 */, false /* c2 */, false /* c3 */);
+
+ mService->markClientForPendingRemoval(kTestPid2, getId(mTestClient2));
+
+ // client marked for pending removal from the same process got reclaimed
+ CHECK_STATUS_TRUE(mService->reclaimResource(kTestPid2, resources, &result));
+ verifyClients(false /* c1 */, true /* c2 */, false /* c3 */);
+
+ // clean up client 3 which still left
+ mService->removeClient(kTestPid2, getId(mTestClient3));
+ }
+
+ {
+ addResource();
+ mService->mSupportsSecureWithNonSecureCodec = true;
+
+ std::vector<MediaResourceParcel> resources;
+ resources.push_back(MediaResource(MediaResource::Type::kNonSecureCodec, 1));
+
+ mService->markClientForPendingRemoval(kTestPid2, getId(mTestClient2));
+
+ // client marked for pending removal from the same process got reclaimed
+ // first, even though there are lower priority process
+ CHECK_STATUS_TRUE(mService->reclaimResource(kTestPid2, resources, &result));
+ verifyClients(false /* c1 */, true /* c2 */, false /* c3 */);
+
+ // lower priority client got reclaimed
+ CHECK_STATUS_TRUE(mService->reclaimResource(kTestPid2, resources, &result));
+ verifyClients(true /* c1 */, false /* c2 */, false /* c3 */);
+
+ // clean up client 3 which still left
+ mService->removeClient(kTestPid2, getId(mTestClient3));
+ }
+ }
+
void testRemoveClient() {
addResource();
@@ -900,4 +950,8 @@
testOverridePid();
}
+TEST_F(ResourceManagerServiceTest, markClientForPendingRemoval) {
+ testMarkClientForPendingRemoval();
+}
+
} // namespace android
diff --git a/services/mediatranscoding/MediaTranscodingService.cpp b/services/mediatranscoding/MediaTranscodingService.cpp
index 0289613..6c10e3e 100644
--- a/services/mediatranscoding/MediaTranscodingService.cpp
+++ b/services/mediatranscoding/MediaTranscodingService.cpp
@@ -22,6 +22,7 @@
#include <android/binder_process.h>
#include <media/TranscodingClientManager.h>
#include <media/TranscodingJobScheduler.h>
+#include <media/TranscodingUidPolicy.h>
#include <private/android_filesystem_config.h>
#include <utils/Log.h>
#include <utils/Vector.h>
@@ -48,10 +49,9 @@
}
}
-// DummyTranscoder and DummyProcessInfo are currently used to instantiate
-// MediaTranscodingService on service side for testing, so that we could
-// actually test the IPC calls of MediaTranscodingService to expose some
-// issues that's observable only over IPC.
+// DummyTranscoder is currently used to instantiate MediaTranscodingService on
+// service side for testing, so that we could actually test the IPC calls of
+// MediaTranscodingService to expose some issues that's observable only over IPC.
class DummyTranscoder : public TranscoderInterface {
void start(int64_t clientId, int32_t jobId) override {
(void)clientId;
@@ -67,23 +67,17 @@
}
};
-class DummyProcessInfo : public ProcessInfoInterface {
- bool isProcessOnTop(int32_t pid) override {
- (void)pid;
- return true;
- }
-};
-
MediaTranscodingService::MediaTranscodingService()
: MediaTranscodingService(std::make_shared<DummyTranscoder>(),
- std::make_shared<DummyProcessInfo>()) {}
+ std::make_shared<TranscodingUidPolicy>()) {}
MediaTranscodingService::MediaTranscodingService(
const std::shared_ptr<TranscoderInterface>& transcoder,
- const std::shared_ptr<ProcessInfoInterface>& procInfo)
- : mJobScheduler(new TranscodingJobScheduler(transcoder, procInfo)),
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy)
+ : mJobScheduler(new TranscodingJobScheduler(transcoder, uidPolicy)),
mClientManager(new TranscodingClientManager(mJobScheduler)) {
ALOGV("MediaTranscodingService is created");
+ uidPolicy->setCallback(mJobScheduler);
}
MediaTranscodingService::~MediaTranscodingService() {
diff --git a/services/mediatranscoding/MediaTranscodingService.h b/services/mediatranscoding/MediaTranscodingService.h
index 36db9b3..f7ac336 100644
--- a/services/mediatranscoding/MediaTranscodingService.h
+++ b/services/mediatranscoding/MediaTranscodingService.h
@@ -31,7 +31,7 @@
class TranscodingClientManager;
class TranscodingJobScheduler;
class TranscoderInterface;
-class ProcessInfoInterface;
+class UidPolicyInterface;
class MediaTranscodingService : public BnMediaTranscodingService {
public:
@@ -40,7 +40,7 @@
MediaTranscodingService();
MediaTranscodingService(const std::shared_ptr<TranscoderInterface>& transcoder,
- const std::shared_ptr<ProcessInfoInterface>& procInfo);
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy);
virtual ~MediaTranscodingService();
static void instantiate();
diff --git a/services/mediatranscoding/mediatranscoding.rc b/services/mediatranscoding/mediatranscoding.rc
index 2dc547f..5bfef59 100644
--- a/services/mediatranscoding/mediatranscoding.rc
+++ b/services/mediatranscoding/mediatranscoding.rc
@@ -3,4 +3,4 @@
user media
group media
ioprio rt 4
- writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+ task_profiles ProcessCapacityHigh HighPerformance