Update mediaformatshaper library
better default handling
defer library load until time of use (memory vs latency)
Bug: 182827840
Test: manual
Change-Id: I557ea3d82814dd5758396181210fd46524a26a30
diff --git a/media/libmediaformatshaper/Android.bp b/media/libmediaformatshaper/Android.bp
index 731ff4c..b283bc8 100644
--- a/media/libmediaformatshaper/Android.bp
+++ b/media/libmediaformatshaper/Android.bp
@@ -36,6 +36,7 @@
name: "libmediaformatshaper_defaults",
srcs: [
"CodecProperties.cpp",
+ "CodecSeeding.cpp",
"FormatShaper.cpp",
"ManageShapingCodecs.cpp",
"VideoShaper.cpp",
diff --git a/media/libmediaformatshaper/CodecProperties.cpp b/media/libmediaformatshaper/CodecProperties.cpp
index dccfd95..d733c57 100644
--- a/media/libmediaformatshaper/CodecProperties.cpp
+++ b/media/libmediaformatshaper/CodecProperties.cpp
@@ -26,6 +26,7 @@
namespace mediaformatshaper {
CodecProperties::CodecProperties(std::string name, std::string mediaType) {
+ ALOGV("CodecProperties(%s, %s)", name.c_str(), mediaType.c_str());
mName = name;
mMediaType = mediaType;
}
@@ -58,6 +59,38 @@
return mApi;
}
+void CodecProperties::setFeatureValue(std::string key, int32_t value) {
+ ALOGD("setFeatureValue(%s,%d)", key.c_str(), value);
+ mFeatures.insert({key, value});
+
+ if (!strcmp(key.c_str(), "vq-minimum-quality")) {
+ setSupportedMinimumQuality(value);
+ } else if (!strcmp(key.c_str(), "vq-supports-qp")) { // key from prototyping
+ setSupportsQp(1);
+ } else if (!strcmp(key.c_str(), "qp-bounds")) { // official key
+ setSupportsQp(1);
+ } else if (!strcmp(key.c_str(), "vq-target-qpmax")) {
+ setTargetQpMax(value);
+ } else if (!strcmp(key.c_str(), "vq-target-bppx100")) {
+ double bpp = value / 100.0;
+ setBpp(bpp);
+ }
+}
+
+bool CodecProperties::getFeatureValue(std::string key, int32_t *valuep) {
+ ALOGV("getFeatureValue(%s)", key.c_str());
+ if (valuep == nullptr) {
+ return false;
+ }
+ auto mapped = mFeatures.find(key);
+ if (mapped != mFeatures.end()) {
+ *valuep = mapped->second;
+ return true;
+ }
+ return false;
+}
+
+
std::string CodecProperties::getMapping(std::string key, std::string kind) {
ALOGV("getMapping(key %s, kind %s )", key.c_str(), kind.c_str());
//play with mMappings
diff --git a/media/libmediaformatshaper/CodecSeeding.cpp b/media/libmediaformatshaper/CodecSeeding.cpp
new file mode 100644
index 0000000..629b405
--- /dev/null
+++ b/media/libmediaformatshaper/CodecSeeding.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "CodecSeeding"
+#include <utils/Log.h>
+
+#include <string>
+
+#include <media/formatshaper/CodecProperties.h>
+
+namespace android {
+namespace mediaformatshaper {
+
+/*
+ * a block of pre-loads; things the library seeds into the codecproperties based
+ * on the mediaType.
+ * XXX: parsing from a file is likely better than embedding in code.
+ */
+typedef struct {
+ const char *key;
+ int32_t value;
+} preloadFeature_t;
+
+typedef struct {
+ const char *mediaType;
+ preloadFeature_t *features;
+} preloadProperties_t;
+
+/*
+ * 240 = 2.4 bits per pixel-per-second == 5mbps@1080, 2.3mbps@720p, which is about where
+ * we want our initial floor for now.
+ */
+
+static preloadFeature_t featuresAvc[] = {
+ {"vq-target-bppx100", 240},
+ {nullptr, 0}
+};
+
+static preloadFeature_t featuresHevc[] = {
+ {"vq-target-bppx100", 240},
+ {nullptr, 0}
+};
+
+static preloadFeature_t featuresGenericVideo[] = {
+ {"vq-target-bppx100", 240},
+ {nullptr, 0}
+};
+
+static preloadProperties_t preloadProperties[] = {
+ { "video/avc", featuresAvc},
+ { "video/hevc", &featuresHevc[0]},
+
+ // wildcard for any video format not already captured
+ { "video/*", &featuresGenericVideo[0]},
+ { nullptr, nullptr}
+};
+
+void CodecProperties::Seed() {
+ ALOGV("Seed: for codec %s, mediatype %s", mName.c_str(), mMediaType.c_str());
+
+ // load me up with initial configuration data
+ int count = 0;
+ for (int i=0;; i++) {
+ preloadProperties_t *p = &preloadProperties[i];
+ if (p->mediaType == nullptr) {
+ break;
+ }
+ bool found = false;
+ if (strcmp(p->mediaType, mMediaType.c_str()) == 0) {
+ found = true;
+ }
+ const char *r;
+ if (!found && (r = strchr(p->mediaType, '*')) != NULL) {
+ // wildcard; check the prefix
+ size_t len = r - p->mediaType;
+ if (strncmp(p->mediaType, mMediaType.c_str(), len) == 0) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ continue;
+ }
+ ALOGV("seeding from mediaType '%s'", p->mediaType);
+
+ // walk through, filling things
+ if (p->features != nullptr) {
+ for (int j=0;; j++) {
+ preloadFeature_t *q = &p->features[j];
+ if (q->key == nullptr) {
+ break;
+ }
+ setFeatureValue(q->key, q->value);
+ count++;
+ }
+ break;
+ }
+ }
+ ALOGV("loaded %d preset values", count);
+}
+
+// a chance, as we register the codec and accept no further updates, to
+// override any poor configuration that arrived from the device's XML files.
+//
+void CodecProperties::Finish() {
+ ALOGV("Finish: for codec %s, mediatype %s", mName.c_str(), mMediaType.c_str());
+
+ // currently a no-op
+}
+
+} // namespace mediaformatshaper
+} // namespace android
+
diff --git a/media/libmediaformatshaper/FormatShaper.cpp b/media/libmediaformatshaper/FormatShaper.cpp
index ca4dc72..a52edc2 100644
--- a/media/libmediaformatshaper/FormatShaper.cpp
+++ b/media/libmediaformatshaper/FormatShaper.cpp
@@ -93,19 +93,9 @@
return -1;
}
- if (!strcmp(feature, "vq-minimum-quality")) {
- codec->setSupportedMinimumQuality(value);
- } else if (!strcmp(feature, "vq-supports-qp")) {
- codec->setSupportsQp(value != 0);
- } else if (!strcmp(feature, "vq-target-qpmax")) {
- codec->setTargetQpMax(value);
- } else if (!strcmp(feature, "vq-target-bppx100")) {
- double bpp = value / 100.0;
- codec->setBpp(bpp);
- } else {
- // changed nothing, don't mark as configured
- return 0;
- }
+ // save a map of all features
+ codec->setFeatureValue(feature, value);
+
return 0;
}
@@ -120,6 +110,9 @@
shaperHandle_t createShaper(const char *codecName, const char *mediaType) {
CodecProperties *codec = new CodecProperties(codecName, mediaType);
+ if (codec != nullptr) {
+ codec->Seed();
+ }
return (shaperHandle_t) codec;
}
@@ -134,6 +127,12 @@
return nullptr;
}
+ // any final cleanup for the parameters. This allows us to override
+ // bad parameters from a devices XML file.
+ codec->Finish();
+
+ // may return a different codec, if we lost a race.
+ // if so, registerCodec() reclaims the one we tried to register for us.
codec = registerCodec(codec, codecName, mediaType);
return (shaperHandle_t) codec;
}
diff --git a/media/libmediaformatshaper/VQApply.cpp b/media/libmediaformatshaper/VQApply.cpp
index 6f6f33c..39a5e19 100644
--- a/media/libmediaformatshaper/VQApply.cpp
+++ b/media/libmediaformatshaper/VQApply.cpp
@@ -44,42 +44,60 @@
#define AMEDIAFORMAT_VIDEO_QP_P_MAX "video-qp-p-max"
#define AMEDIAFORMAT_VIDEO_QP_P_MIN "video-qp-p-min"
+// defined in the SDK, but not in the NDK
+//
+static const int BITRATE_MODE_VBR = 1;
+
//
// Caller retains ownership of and responsibility for inFormat
//
int VQApply(CodecProperties *codec, vqOps_t *info, AMediaFormat* inFormat, int flags) {
ALOGV("codecName %s inFormat %p flags x%x", codec->getName().c_str(), inFormat, flags);
- if (codec->supportedMinimumQuality() > 0) {
- // allow the codec provided minimum quality behavior to work at it
- ALOGD("minquality(codec): codec says %d", codec->supportedMinimumQuality());
+ int32_t bitRateMode = -1;
+ if (AMediaFormat_getInt32(inFormat, AMEDIAFORMAT_KEY_BITRATE_MODE, &bitRateMode)
+ && bitRateMode != BITRATE_MODE_VBR) {
+ ALOGD("minquality: applies only to VBR encoding");
return 0;
}
- ALOGD("considering other ways to improve quality...");
+ if (codec->supportedMinimumQuality() > 0) {
+ // allow the codec provided minimum quality behavior to work at it
+ ALOGD("minquality: codec claims to implement minquality=%d",
+ codec->supportedMinimumQuality());
+ return 0;
+ }
//
// apply any and all tools that we have.
// -- qp
// -- minimum bits-per-pixel
//
- if (codec->supportsQp()) {
+ if (!codec->supportsQp()) {
+ ALOGD("minquality: no qp bounding in codec %s", codec->getName().c_str());
+ } else {
// use a (configurable) QP value to force better quality
//
- // XXX: augment this so that we don't lower an existing QP setting
- // (e.g. if user set it to 40, we don't want to set it back to 45)
- int qpmax = codec->targetQpMax();
- if (qpmax <= 0) {
- qpmax = 45;
- ALOGD("use default substitute QpMax == %d", qpmax);
+ int32_t qpmax = codec->targetQpMax();
+ int32_t qpmaxUser = INT32_MAX;
+ if (hasQp(inFormat)) {
+ (void) AMediaFormat_getInt32(inFormat, AMEDIAFORMAT_VIDEO_QP_MAX, &qpmaxUser);
+ ALOGD("minquality by QP: format already sets QP");
}
- ALOGD("minquality by QP: inject %s=%d", AMEDIAFORMAT_VIDEO_QP_MAX, qpmax);
- AMediaFormat_setInt32(inFormat, AMEDIAFORMAT_VIDEO_QP_MAX, qpmax);
- // force spreading the QP across frame types, since we imposing a value
- qpSpreadMaxPerFrameType(inFormat, info->qpDelta, info->qpMax, /* override */ true);
- } else {
- ALOGD("codec %s: no qp bounding", codec->getName().c_str());
+ // if the system didn't do one, use what the user provided
+ if (qpmax == 0 && qpmaxUser != INT32_MAX) {
+ qpmax = qpmaxUser;
+ }
+ // XXX: if both said something, how do we want to reconcile that
+
+ if (qpmax > 0) {
+ ALOGD("minquality by QP: inject %s=%d", AMEDIAFORMAT_VIDEO_QP_MAX, qpmax);
+ AMediaFormat_setInt32(inFormat, AMEDIAFORMAT_VIDEO_QP_MAX, qpmax);
+
+ // force spreading the QP across frame types, since we imposing a value
+ qpSpreadMaxPerFrameType(inFormat, info->qpDelta, info->qpMax, /* override */ true);
+ }
}
double bpp = codec->getBpp();
@@ -108,7 +126,7 @@
bitrateConfigured, bitrateFloor, codec->getBpp(), height, width);
if (bitrateConfigured < bitrateFloor) {
- ALOGD("minquality/target bitrate raised from %d to %" PRId64 " to maintain quality",
+ ALOGD("minquality/target bitrate raised from %d to %" PRId64 " bps",
bitrateConfigured, bitrateFloor);
AMediaFormat_setInt32(inFormat, AMEDIAFORMAT_KEY_BIT_RATE, (int32_t)bitrateFloor);
}
@@ -121,16 +139,16 @@
bool hasQpPerFrameType(AMediaFormat *format) {
int32_t value;
- if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_I_MAX, &value)
- || !AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_I_MIN, &value)) {
+ if (AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_I_MAX, &value)
+ || AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_I_MIN, &value)) {
return true;
}
- if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_P_MAX, &value)
- || !AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_P_MIN, &value)) {
+ if (AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_P_MAX, &value)
+ || AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_P_MIN, &value)) {
return true;
}
- if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_B_MAX, &value)
- || !AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_B_MIN, &value)) {
+ if (AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_B_MAX, &value)
+ || AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_B_MIN, &value)) {
return true;
}
return false;
@@ -138,8 +156,8 @@
bool hasQp(AMediaFormat *format) {
int32_t value;
- if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_MAX, &value)
- || !AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_MIN, &value)) {
+ if (AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_MAX, &value)
+ || AMediaFormat_getInt32(format, AMEDIAFORMAT_VIDEO_QP_MIN, &value)) {
return true;
}
return hasQpPerFrameType(format);
diff --git a/media/libmediaformatshaper/VideoShaper.cpp b/media/libmediaformatshaper/VideoShaper.cpp
index fecd3a1..f772a66 100644
--- a/media/libmediaformatshaper/VideoShaper.cpp
+++ b/media/libmediaformatshaper/VideoShaper.cpp
@@ -83,7 +83,7 @@
// apply any quality transforms in here..
(void) VQApply(codec, info, inFormat, flags);
- // We must always spread and map any QP parameters.
+ // We must always spread any QP parameters.
// Sometimes it's something we inserted here, sometimes it's a value that the user injected.
//
qpSpreadPerFrameType(inFormat, info->qpDelta, info->qpMin, info->qpMax, /* override */ false);
diff --git a/media/libmediaformatshaper/include/media/formatshaper/CodecProperties.h b/media/libmediaformatshaper/include/media/formatshaper/CodecProperties.h
index f7177a4..e5cc9cf 100644
--- a/media/libmediaformatshaper/include/media/formatshaper/CodecProperties.h
+++ b/media/libmediaformatshaper/include/media/formatshaper/CodecProperties.h
@@ -31,6 +31,12 @@
public:
CodecProperties(std::string name, std::string mediaType);
+ // seed the codec with some preconfigured values
+ // (e.g. mediaType-granularity defaults)
+ // runs from the constructor
+ void Seed();
+ void Finish();
+
std::string getName();
std::string getMediaType();
@@ -46,8 +52,9 @@
// and 'reverse' describes which strings are to be on which side.
const char **getMappings(std::string kind, bool reverse);
- // debugging of what's in the mapping dictionary
- void showMappings();
+ // keep a map of all features and their parameters
+ void setFeatureValue(std::string key, int32_t value);
+ bool getFeatureValue(std::string key, int32_t *valuep);
// does the codec support the Android S minimum quality rules
void setSupportedMinimumQuality(int vmaf);
@@ -88,10 +95,14 @@
std::mutex mMappingLock;
// XXX figure out why I'm having problems getting compiler to like GUARDED_BY
std::map<std::string, std::string> mMappings /*GUARDED_BY(mMappingLock)*/ ;
- std::map<std::string, std::string> mUnMappings /*GUARDED_BY(mMappingLock)*/ ;
+
+ std::map<std::string, int32_t> mFeatures /*GUARDED_BY(mMappingLock)*/ ;
bool mIsRegistered = false;
+ // debugging of what's in the mapping dictionary
+ void showMappings();
+
// DISALLOW_EVIL_CONSTRUCTORS(CodecProperties);
};
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index be21a5d..26cdec8 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -1390,6 +1390,11 @@
return msg->post();
}
+/*
+ * MediaFormat Shaping forward declarations
+ * including the property name we use for control.
+ */
+static const char enableMediaFormatShapingProperty[] = "debug.stagefright.enableshaping";
static void mapFormat(AString componentName, const sp<AMessage> &format, const char *kind,
bool reverse);
@@ -1463,15 +1468,13 @@
}
}
- // apply framework level modifications to the mediaformat for encoding
- // XXX: default off for a while during dogfooding
- static const char *enable_property = "debug.stagefright.enableshaping";
- int8_t enableShaping = property_get_bool(enable_property, 0);
- if (!enableShaping) {
- ALOGD("format shaping disabled via property '%s'", enable_property);
- } else {
- if (flags & CONFIGURE_FLAG_ENCODE) {
+ if (flags & CONFIGURE_FLAG_ENCODE) {
+ int8_t enableShaping = property_get_bool(enableMediaFormatShapingProperty, 0);
+ if (!enableShaping) {
+ ALOGI("format shaping disabled, property '%s'", enableMediaFormatShapingProperty);
+ } else {
(void) shapeMediaFormat(format, flags);
+ // XXX: do we want to do this regardless of shaping enablement?
mapFormat(mComponentName, format, nullptr, false);
}
}
@@ -1552,10 +1555,25 @@
static bool connectFormatShaper() {
static std::once_flag sCheckOnce;
- static void *libHandle = NULL;
+
+#if 0
+ // an early return if the property says disabled means we skip loading.
+ // that saves memory.
+
+ // apply framework level modifications to the mediaformat for encoding
+ // XXX: default off for a while during dogfooding
+ int8_t enableShaping = property_get_bool(enableMediaFormatShapingProperty, 0);
+
+ if (!enableShaping) {
+ return true;
+ }
+#endif
std::call_once(sCheckOnce, [&](){
+ void *libHandle = NULL;
+ nsecs_t loading_started = systemTime(SYSTEM_TIME_MONOTONIC);
+
// prefer any copy in the mainline module
//
android_namespace_t *mediaNs = android_get_exported_namespace("com_android_media");
@@ -1614,44 +1632,33 @@
ALOGV("connectFormatShaper: connected to library %s", libraryName.c_str());
}
+ nsecs_t loading_finished = systemTime(SYSTEM_TIME_MONOTONIC);
+ ALOGV("connectFormatShaper: loaded libraries: %" PRId64 " us",
+ (loading_finished - loading_started)/1000);
+
});
return true;
}
+
+#if 0
// a construct to force the above dlopen() to run very early.
// goal: so the dlopen() doesn't happen on critical path of latency sensitive apps
// failure of this means that cold start of those apps is slower by the time to dlopen()
+// TODO(b/183454066): tradeoffs between memory of early loading vs latency of late loading
//
static bool forceEarlyLoadingShaper = connectFormatShaper();
+#endif
// parse the codec's properties: mapping, whether it meets min quality, etc
// and pass them into the video quality code
//
-status_t MediaCodec::setupFormatShaper(AString mediaType) {
- ALOGV("setupFormatShaper: initializing shaper data for codec %s mediaType %s",
- mComponentName.c_str(), mediaType.c_str());
-
- nsecs_t mapping_started = systemTime(SYSTEM_TIME_MONOTONIC);
-
- // see if the shaper is already present, if so return
- mediaformatshaper::shaperHandle_t shaperHandle;
- shaperHandle = sShaperOps->findShaper(mComponentName.c_str(), mediaType.c_str());
- if (shaperHandle != nullptr) {
- ALOGV("shaperhandle %p -- no initialization needed", shaperHandle);
- return OK;
- }
-
- // not there, so we get to build & register one
- shaperHandle = sShaperOps->createShaper(mComponentName.c_str(), mediaType.c_str());
- if (shaperHandle == nullptr) {
- ALOGW("unable to create a shaper for cocodec %s mediaType %s",
- mComponentName.c_str(), mediaType.c_str());
- return OK;
- }
+static void loadCodecProperties(mediaformatshaper::shaperHandle_t shaperHandle,
+ sp<MediaCodecInfo> codecInfo, AString mediaType) {
sp<MediaCodecInfo::Capabilities> capabilities =
- mCodecInfo->getCapabilitiesFor(mediaType.c_str());
+ codecInfo->getCapabilitiesFor(mediaType.c_str());
if (capabilities == nullptr) {
ALOGI("no capabilities as part of the codec?");
} else {
@@ -1697,11 +1704,37 @@
}
}
}
+}
+
+status_t MediaCodec::setupFormatShaper(AString mediaType) {
+ ALOGV("setupFormatShaper: initializing shaper data for codec %s mediaType %s",
+ mComponentName.c_str(), mediaType.c_str());
+
+ nsecs_t mapping_started = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ // someone might have beaten us to it.
+ mediaformatshaper::shaperHandle_t shaperHandle;
+ shaperHandle = sShaperOps->findShaper(mComponentName.c_str(), mediaType.c_str());
+ if (shaperHandle != nullptr) {
+ ALOGV("shaperhandle %p -- no initialization needed", shaperHandle);
+ return OK;
+ }
+
+ // we get to build & register one
+ shaperHandle = sShaperOps->createShaper(mComponentName.c_str(), mediaType.c_str());
+ if (shaperHandle == nullptr) {
+ ALOGW("unable to create a shaper for cocodec %s mediaType %s",
+ mComponentName.c_str(), mediaType.c_str());
+ return OK;
+ }
+
+ (void) loadCodecProperties(shaperHandle, mCodecInfo, mediaType);
+
shaperHandle = sShaperOps->registerShaper(shaperHandle,
mComponentName.c_str(), mediaType.c_str());
nsecs_t mapping_finished = systemTime(SYSTEM_TIME_MONOTONIC);
- ALOGD("setupFormatShaper: populated shaper node for codec %s: %" PRId64 " us",
+ ALOGV("setupFormatShaper: populated shaper node for codec %s: %" PRId64 " us",
mComponentName.c_str(), (mapping_finished - mapping_started)/1000);
return OK;
@@ -1791,9 +1824,12 @@
// make sure we have the function entry points for the shaper library
//
+#if 0
+ // let's play the faster "only do mapping if we've already loaded the library
connectFormatShaper();
+#endif
if (sShaperOps == nullptr) {
- ALOGW("mapFormat: no MediaFormatShaper hooks available");
+ ALOGV("mapFormat: no MediaFormatShaper hooks available");
return;
}