| /* |
| * 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 "CodecProperties" |
| #include <utils/Log.h> |
| |
| #include <string> |
| #include <stdlib.h> |
| |
| #include "CodecProperties.h" |
| |
| #include <media/stagefright/MediaCodecConstants.h> |
| |
| |
| // we aren't going to mess with shaping points dimensions beyond this |
| static const int32_t DIMENSION_LIMIT = 16384; |
| |
| namespace android { |
| namespace mediaformatshaper { |
| |
| CodecProperties::CodecProperties(std::string name, std::string mediaType) { |
| ALOGV("CodecProperties(%s, %s)", name.c_str(), mediaType.c_str()); |
| mName = name; |
| mMediaType = mediaType; |
| } |
| |
| std::string CodecProperties::getName(){ |
| return mName; |
| } |
| |
| std::string CodecProperties::getMediaType(){ |
| return mMediaType; |
| } |
| |
| int CodecProperties::supportedMinimumQuality() { |
| return mMinimumQuality; |
| } |
| void CodecProperties::setSupportedMinimumQuality(int vmaf) { |
| mMinimumQuality = vmaf; |
| } |
| |
| void CodecProperties::setMissingQpBoost(double boost) { |
| mMissingQpBoost = boost; |
| } |
| void CodecProperties::setPhaseOut(double phaseout) { |
| mPhaseOut = phaseout; |
| } |
| |
| // what API is this codec set up for (e.g. API of the associated partition) |
| // vendor-side (OEM) codecs may be older, due to 'vendor freeze' and treble |
| int CodecProperties::supportedApi() { |
| 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(), FEATURE_QpBounds)) { |
| setSupportsQp(1); |
| } else if (!strcmp(key.c_str(), "video-minimum-quality")) { |
| setSupportedMinimumQuality(1); |
| } else if (!strcmp(key.c_str(), "vq-minimum-quality")) { // from prototyping |
| setSupportedMinimumQuality(1); |
| } |
| } |
| |
| 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; |
| } |
| |
| // Tuning values (which differ from Features) |
| // this is where we set up things like target bitrates and QP ranges |
| // NB the tuning values arrive as a string, allowing us to convert it into an appropriate |
| // format (int, float, ranges, other combinations) |
| // |
| void CodecProperties::setTuningValue(std::string key, std::string value) { |
| ALOGD("setTuningValue(%s,%s)", key.c_str(), value.c_str()); |
| mTunings.insert({key, value}); |
| |
| bool legal = false; |
| // NB: old school strtol() because std::stoi() throws exceptions |
| if (!strcmp(key.c_str(), "vq-target-qpmax")) { |
| const char *p = value.c_str(); |
| char *q; |
| int32_t iValue = strtol(p, &q, 0); |
| if (q != p) { |
| setTargetQpMax(iValue); |
| legal = true; |
| } |
| } else if (!strncmp(key.c_str(), "vq-target-qpmax-", strlen("vq-target-qpmax-"))) { |
| std::string resolution = key.substr(strlen("vq-target-qpmax-")); |
| if (qpMaxPoint(resolution, value)) { |
| legal = true; |
| } |
| } else if (!strcmp(key.c_str(), "vq-target-bpp")) { |
| const char *p = value.c_str(); |
| char *q; |
| double bpp = strtod(p, &q); |
| if (q != p) { |
| setBpp(bpp); |
| legal = true; |
| } |
| } else if (!strncmp(key.c_str(), "vq-target-bpp-", strlen("vq-target-bpp-"))) { |
| std::string resolution = key.substr(strlen("vq-target-bpp-")); |
| if (bppPoint(resolution, value)) { |
| legal = true; |
| } |
| } else if (!strcmp(key.c_str(), "vq-target-bppx100")) { |
| // legacy, prototyping |
| const char *p = value.c_str(); |
| char *q; |
| int32_t iValue = strtol(p, &q, 0); |
| if (q != p) { |
| double bpp = iValue / 100.0; |
| setBpp(bpp); |
| legal = true; |
| } |
| } else if (!strcmp(key.c_str(), "vq-bitrate-phaseout")) { |
| const char *p = value.c_str(); |
| char *q; |
| double phaseout = strtod(p, &q); |
| if (q != p) { |
| setPhaseOut(phaseout); |
| legal = true; |
| } |
| } else if (!strcmp(key.c_str(), "vq-boost-missing-qp")) { |
| const char *p = value.c_str(); |
| char *q; |
| double boost = strtod(p, &q); |
| if (q != p) { |
| setMissingQpBoost(boost); |
| legal = true; |
| } |
| } else { |
| legal = true; |
| } |
| |
| if (!legal) { |
| ALOGW("setTuningValue() unable to apply tuning '%s' with value '%s'", |
| key.c_str(), value.c_str()); |
| } |
| return; |
| } |
| |
| bool CodecProperties::getTuningValue(std::string key, std::string &value) { |
| ALOGV("getTuningValue(%s)", key.c_str()); |
| auto mapped = mFeatures.find(key); |
| if (mapped != mFeatures.end()) { |
| value = mapped->second; |
| return true; |
| } |
| return false; |
| } |
| |
| bool CodecProperties::bppPoint(std::string resolution, std::string value) { |
| |
| int32_t width = 0; |
| int32_t height = 0; |
| double bpp = -1; |
| |
| // resolution is "WxH", "W*H" or a standard name like "720p" |
| if (resolution == "1080p") { |
| width = 1080; height = 1920; |
| } else if (resolution == "720p") { |
| width = 720; height = 1280; |
| } else if (resolution == "540p") { |
| width = 540; height = 960; |
| } else if (resolution == "480p") { |
| width = 480; height = 854; |
| } else { |
| size_t sep = resolution.find('x'); |
| if (sep == std::string::npos) { |
| sep = resolution.find('*'); |
| } |
| if (sep == std::string::npos) { |
| ALOGW("unable to parse resolution: '%s'", resolution.c_str()); |
| return false; |
| } |
| std::string w = resolution.substr(0, sep); |
| std::string h = resolution.substr(sep+1); |
| |
| char *q; |
| const char *p = w.c_str(); |
| width = strtol(p, &q, 0); |
| if (q == p) { |
| width = -1; |
| } |
| p = h.c_str(); |
| height = strtol(p, &q, 0); |
| if (q == p) { |
| height = -1; |
| } |
| if (width <= 0 || height <= 0 || width > DIMENSION_LIMIT || height > DIMENSION_LIMIT) { |
| ALOGW("unparseable: width, height '%s'", resolution.c_str()); |
| return false; |
| } |
| } |
| |
| const char *p = value.c_str(); |
| char *q; |
| bpp = strtod(p, &q); |
| if (q == p) { |
| ALOGW("unparseable bpp '%s'", value.c_str()); |
| return false; |
| } |
| |
| struct bpp_point *point = (struct bpp_point*) malloc(sizeof(*point)); |
| if (point == nullptr) { |
| ALOGW("unable to allocate memory for bpp point"); |
| return false; |
| } |
| |
| point->pixels = width * height; |
| point->width = width; |
| point->height = height; |
| point->bpp = bpp; |
| |
| if (mBppPoints == nullptr) { |
| point->next = nullptr; |
| mBppPoints = point; |
| } else if (point->pixels < mBppPoints->pixels) { |
| // at the front |
| point->next = mBppPoints; |
| mBppPoints = point; |
| } else { |
| struct bpp_point *after = mBppPoints; |
| while (after->next) { |
| if (point->pixels > after->next->pixels) { |
| after = after->next; |
| continue; |
| } |
| |
| // insert before after->next |
| point->next = after->next; |
| after->next = point; |
| break; |
| } |
| if (after->next == nullptr) { |
| // hasn't gone in yet |
| point->next = nullptr; |
| after->next = point; |
| } |
| } |
| |
| return true; |
| } |
| |
| double CodecProperties::getBpp(int32_t width, int32_t height) { |
| // look in the per-resolution list |
| |
| int32_t pixels = width * height; |
| |
| if (mBppPoints) { |
| struct bpp_point *point = mBppPoints; |
| while (point && point->pixels < pixels) { |
| point = point->next; |
| } |
| if (point) { |
| ALOGV("getBpp(w=%d,h=%d) returns %f from bpppoint w=%d h=%d", |
| width, height, point->bpp, point->width, point->height); |
| return point->bpp; |
| } |
| } |
| |
| ALOGV("defaulting to %f bpp", mBpp); |
| return mBpp; |
| } |
| |
| bool CodecProperties::qpMaxPoint(std::string resolution, std::string value) { |
| |
| int32_t width = 0; |
| int32_t height = 0; |
| int qpMax = INT32_MAX; |
| |
| // resolution is "WxH", "W*H" or a standard name like "720p" |
| if (resolution == "1080p") { |
| width = 1080; height = 1920; |
| } else if (resolution == "720p") { |
| width = 720; height = 1280; |
| } else if (resolution == "540p") { |
| width = 540; height = 960; |
| } else if (resolution == "480p") { |
| width = 480; height = 854; |
| } else { |
| size_t sep = resolution.find('x'); |
| if (sep == std::string::npos) { |
| sep = resolution.find('*'); |
| } |
| if (sep == std::string::npos) { |
| ALOGW("unable to parse resolution: '%s'", resolution.c_str()); |
| return false; |
| } |
| std::string w = resolution.substr(0, sep); |
| std::string h = resolution.substr(sep+1); |
| |
| char *q; |
| const char *p = w.c_str(); |
| width = strtol(p, &q, 0); |
| if (q == p) { |
| width = -1; |
| } |
| p = h.c_str(); |
| height = strtol(p, &q, 0); |
| if (q == p) { |
| height = -1; |
| } |
| if (width <= 0 || height <= 0 || width > DIMENSION_LIMIT || height > DIMENSION_LIMIT) { |
| ALOGW("unparseable: width, height '%s'", resolution.c_str()); |
| return false; |
| } |
| } |
| |
| const char *p = value.c_str(); |
| char *q; |
| qpMax = strtol(p, &q, 0); |
| if (q == p) { |
| ALOGW("unparseable qpmax '%s'", value.c_str()); |
| return false; |
| } |
| |
| // convert to our internal 'unspecified' notation |
| if (qpMax == -1) |
| qpMax = INT32_MAX; |
| |
| struct qpmax_point *point = (struct qpmax_point*) malloc(sizeof(*point)); |
| if (point == nullptr) { |
| ALOGW("unable to allocate memory for qpmax point"); |
| return false; |
| } |
| |
| point->pixels = width * height; |
| point->width = width; |
| point->height = height; |
| point->qpMax = qpMax; |
| |
| if (mQpMaxPoints == nullptr) { |
| point->next = nullptr; |
| mQpMaxPoints = point; |
| } else if (point->pixels < mQpMaxPoints->pixels) { |
| // at the front |
| point->next = mQpMaxPoints; |
| mQpMaxPoints = point; |
| } else { |
| struct qpmax_point *after = mQpMaxPoints; |
| while (after->next) { |
| if (point->pixels > after->next->pixels) { |
| after = after->next; |
| continue; |
| } |
| |
| // insert before after->next |
| point->next = after->next; |
| after->next = point; |
| break; |
| } |
| if (after->next == nullptr) { |
| // hasn't gone in yet |
| point->next = nullptr; |
| after->next = point; |
| } |
| } |
| |
| return true; |
| } |
| |
| int CodecProperties::targetQpMax(int32_t width, int32_t height) { |
| // look in the per-resolution list |
| |
| int32_t pixels = width * height; |
| |
| if (mQpMaxPoints) { |
| struct qpmax_point *point = mQpMaxPoints; |
| while (point && point->pixels < pixels) { |
| point = point->next; |
| } |
| if (point) { |
| ALOGV("targetQpMax(w=%d,h=%d) returns %d from qpmax_point w=%d h=%d", |
| width, height, point->qpMax, point->width, point->height); |
| return point->qpMax; |
| } |
| } |
| |
| ALOGV("defaulting to %d qpmax", mTargetQpMax); |
| return mTargetQpMax; |
| } |
| |
| void CodecProperties::setTargetQpMax(int qpMax) { |
| // convert to our internal 'unspecified' notation |
| if (qpMax == -1) |
| qpMax = INT32_MAX; |
| mTargetQpMax = qpMax; |
| } |
| |
| 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 |
| auto mapped = mMappings.find(kind + "-" + key); |
| if (mapped != mMappings.end()) { |
| std::string result = mapped->second; |
| ALOGV("getMapping(%s, %s) -> %s", key.c_str(), kind.c_str(), result.c_str()); |
| return result; |
| } |
| ALOGV("nope, return unchanged key"); |
| return key; |
| } |
| |
| |
| // really a bit of debugging code here. |
| void CodecProperties::showMappings() { |
| ALOGD("Mappings:"); |
| int count = 0; |
| for (const auto& [key, value] : mMappings) { |
| count++; |
| ALOGD("'%s' -> '%s'", key.c_str(), value.c_str()); |
| } |
| ALOGD("total %d mappings", count); |
| } |
| |
| void CodecProperties::setMapping(std::string kind, std::string key, std::string value) { |
| ALOGV("setMapping(%s,%s,%s)", kind.c_str(), key.c_str(), value.c_str()); |
| std::string metaKey = kind + "-" + key; |
| mMappings.insert({metaKey, value}); |
| } |
| |
| const char **CodecProperties::getMappings(std::string kind, bool reverse) { |
| ALOGV("getMappings(kind %s, reverse %d", kind.c_str(), reverse); |
| // how many do we need? |
| int count = mMappings.size(); |
| if (count == 0) { |
| ALOGV("empty mappings"); |
| return nullptr; |
| } |
| size_t size = sizeof(char *) * (2 * count + 2); |
| const char **result = (const char **)malloc(size); |
| if (result == nullptr) { |
| ALOGW("no memory to return mappings"); |
| return nullptr; |
| } |
| memset(result, '\0', size); |
| |
| const char **pp = result; |
| for (const auto& [key, value] : mMappings) { |
| // split out the kind/key |
| size_t pos = key.find('-'); |
| if (pos == std::string::npos) { |
| ALOGD("ignoring malformed key: %s", key.c_str()); |
| continue; |
| } |
| std::string actualKind = key.substr(0,pos); |
| if (kind.length() != 0 && kind != actualKind) { |
| ALOGD("kinds don't match: want '%s' got '%s'", kind.c_str(), actualKind.c_str()); |
| continue; |
| } |
| if (reverse) { |
| // codec specific -> std aka 'unmapping' |
| pp[0] = strdup( value.c_str()); |
| pp[1] = strdup( key.substr(pos+1).c_str()); |
| } else { |
| // std -> codec specific |
| pp[0] = strdup( key.substr(pos+1).c_str()); |
| pp[1] = strdup( value.c_str()); |
| } |
| ALOGV(" %s -> %s", pp[0], pp[1]); |
| pp += 2; |
| } |
| |
| pp[0] = nullptr; |
| pp[1] = nullptr; |
| |
| return result; |
| } |
| |
| |
| } // namespace mediaformatshaper |
| } // namespace android |
| |