blob: 84e5514ddac5f35ba87d5d7d01f7ed0a8eeb0cc2 [file] [log] [blame]
/*
* Copyright 2017, 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 "MediaCodecsXmlParser"
#include <utils/Log.h>
#include <media/vndk/xmlparser/1.0/MediaCodecsXmlParser.h>
#include <media/MediaCodecInfo.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/AUtils.h>
#include <media/stagefright/MediaErrors.h>
#include <sys/stat.h>
#include <expat.h>
#include <string>
#define MEDIA_CODECS_CONFIG_FILE_PATH_MAX_LENGTH 256
namespace android {
namespace { // Local variables and functions
const char *kProfilingResults =
"/data/misc/media/media_codecs_profiling_results.xml";
// Treblized media codec list will be located in /odm/etc or /vendor/etc.
const char *kConfigLocationList[] =
{"/odm/etc", "/vendor/etc", "/etc"};
constexpr int kConfigLocationListSize =
(sizeof(kConfigLocationList) / sizeof(kConfigLocationList[0]));
bool findMediaCodecListFileFullPath(
const char *file_name, std::string *out_path) {
for (int i = 0; i < kConfigLocationListSize; i++) {
*out_path = std::string(kConfigLocationList[i]) + "/" + file_name;
struct stat file_stat;
if (stat(out_path->c_str(), &file_stat) == 0 &&
S_ISREG(file_stat.st_mode)) {
return true;
}
}
return false;
}
// Find TypeInfo by name.
std::vector<TypeInfo>::iterator findTypeInfo(
CodecInfo &codecInfo, const AString &typeName) {
return std::find_if(
codecInfo.mTypes.begin(), codecInfo.mTypes.end(),
[typeName](const auto &typeInfo) {
return typeInfo.mName == typeName;
});
}
// Convert a string into a boolean value.
bool ParseBoolean(const char *s) {
if (!strcasecmp(s, "true") || !strcasecmp(s, "yes") || !strcasecmp(s, "y")) {
return true;
}
char *end;
unsigned long res = strtoul(s, &end, 10);
return *s != '\0' && *end == '\0' && res > 0;
}
} // unnamed namespace
MediaCodecsXmlParser::MediaCodecsXmlParser() :
mInitCheck(NO_INIT),
mUpdate(false) {
std::string config_file_path;
if (findMediaCodecListFileFullPath(
"media_codecs.xml", &config_file_path)) {
parseTopLevelXMLFile(config_file_path.c_str(), false);
} else {
mInitCheck = NAME_NOT_FOUND;
}
if (findMediaCodecListFileFullPath(
"media_codecs_performance.xml", &config_file_path)) {
parseTopLevelXMLFile(config_file_path.c_str(), true);
}
parseTopLevelXMLFile(kProfilingResults, true);
}
void MediaCodecsXmlParser::parseTopLevelXMLFile(
const char *codecs_xml, bool ignore_errors) {
// get href_base
const char *href_base_end = strrchr(codecs_xml, '/');
if (href_base_end != NULL) {
mHrefBase = AString(codecs_xml, href_base_end - codecs_xml + 1);
}
mInitCheck = OK; // keeping this here for safety
mCurrentSection = SECTION_TOPLEVEL;
mDepth = 0;
parseXMLFile(codecs_xml);
if (mInitCheck != OK) {
if (ignore_errors) {
mInitCheck = OK;
return;
}
mCodecInfos.clear();
return;
}
}
MediaCodecsXmlParser::~MediaCodecsXmlParser() {
}
status_t MediaCodecsXmlParser::initCheck() const {
return mInitCheck;
}
void MediaCodecsXmlParser::parseXMLFile(const char *path) {
FILE *file = fopen(path, "r");
if (file == NULL) {
ALOGW("unable to open media codecs configuration xml file: %s", path);
mInitCheck = NAME_NOT_FOUND;
return;
}
ALOGV("Start parsing %s", path);
XML_Parser parser = ::XML_ParserCreate(NULL);
CHECK(parser != NULL);
::XML_SetUserData(parser, this);
::XML_SetElementHandler(
parser, StartElementHandlerWrapper, EndElementHandlerWrapper);
const int BUFF_SIZE = 512;
while (mInitCheck == OK) {
void *buff = ::XML_GetBuffer(parser, BUFF_SIZE);
if (buff == NULL) {
ALOGE("failed in call to XML_GetBuffer()");
mInitCheck = UNKNOWN_ERROR;
break;
}
int bytes_read = ::fread(buff, 1, BUFF_SIZE, file);
if (bytes_read < 0) {
ALOGE("failed in call to read");
mInitCheck = ERROR_IO;
break;
}
XML_Status status = ::XML_ParseBuffer(parser, bytes_read, bytes_read == 0);
if (status != XML_STATUS_OK) {
ALOGE("malformed (%s)", ::XML_ErrorString(::XML_GetErrorCode(parser)));
mInitCheck = ERROR_MALFORMED;
break;
}
if (bytes_read == 0) {
break;
}
}
::XML_ParserFree(parser);
fclose(file);
file = NULL;
}
// static
void MediaCodecsXmlParser::StartElementHandlerWrapper(
void *me, const char *name, const char **attrs) {
static_cast<MediaCodecsXmlParser *>(me)->startElementHandler(name, attrs);
}
// static
void MediaCodecsXmlParser::EndElementHandlerWrapper(void *me, const char *name) {
static_cast<MediaCodecsXmlParser *>(me)->endElementHandler(name);
}
status_t MediaCodecsXmlParser::includeXMLFile(const char **attrs) {
const char *href = NULL;
size_t i = 0;
while (attrs[i] != NULL) {
if (!strcmp(attrs[i], "href")) {
if (attrs[i + 1] == NULL) {
return -EINVAL;
}
href = attrs[i + 1];
++i;
} else {
ALOGE("includeXMLFile: unrecognized attribute: %s", attrs[i]);
return -EINVAL;
}
++i;
}
// For security reasons and for simplicity, file names can only contain
// [a-zA-Z0-9_.] and must start with media_codecs_ and end with .xml
for (i = 0; href[i] != '\0'; i++) {
if (href[i] == '.' || href[i] == '_' ||
(href[i] >= '0' && href[i] <= '9') ||
(href[i] >= 'A' && href[i] <= 'Z') ||
(href[i] >= 'a' && href[i] <= 'z')) {
continue;
}
ALOGE("invalid include file name: %s", href);
return -EINVAL;
}
AString filename = href;
if (!filename.startsWith("media_codecs_") ||
!filename.endsWith(".xml")) {
ALOGE("invalid include file name: %s", href);
return -EINVAL;
}
filename.insert(mHrefBase, 0);
parseXMLFile(filename.c_str());
return mInitCheck;
}
void MediaCodecsXmlParser::startElementHandler(
const char *name, const char **attrs) {
if (mInitCheck != OK) {
return;
}
bool inType = true;
if (!strcmp(name, "Include")) {
mInitCheck = includeXMLFile(attrs);
if (mInitCheck == OK) {
mPastSections.push(mCurrentSection);
mCurrentSection = SECTION_INCLUDE;
}
++mDepth;
return;
}
switch (mCurrentSection) {
case SECTION_TOPLEVEL:
{
if (!strcmp(name, "Decoders")) {
mCurrentSection = SECTION_DECODERS;
} else if (!strcmp(name, "Encoders")) {
mCurrentSection = SECTION_ENCODERS;
} else if (!strcmp(name, "Settings")) {
mCurrentSection = SECTION_SETTINGS;
}
break;
}
case SECTION_SETTINGS:
{
if (!strcmp(name, "Setting")) {
mInitCheck = addSettingFromAttributes(attrs);
}
break;
}
case SECTION_DECODERS:
{
if (!strcmp(name, "MediaCodec")) {
mInitCheck =
addMediaCodecFromAttributes(false /* encoder */, attrs);
mCurrentSection = SECTION_DECODER;
}
break;
}
case SECTION_ENCODERS:
{
if (!strcmp(name, "MediaCodec")) {
mInitCheck =
addMediaCodecFromAttributes(true /* encoder */, attrs);
mCurrentSection = SECTION_ENCODER;
}
break;
}
case SECTION_DECODER:
case SECTION_ENCODER:
{
if (!strcmp(name, "Quirk")) {
mInitCheck = addQuirk(attrs);
} else if (!strcmp(name, "Type")) {
mInitCheck = addTypeFromAttributes(attrs, (mCurrentSection == SECTION_ENCODER));
mCurrentSection =
(mCurrentSection == SECTION_DECODER
? SECTION_DECODER_TYPE : SECTION_ENCODER_TYPE);
}
}
inType = false;
// fall through
case SECTION_DECODER_TYPE:
case SECTION_ENCODER_TYPE:
{
// ignore limits and features specified outside of type
bool outside = !inType && mCurrentType == mCodecInfos[mCurrentName].mTypes.end();
if (outside && (!strcmp(name, "Limit") || !strcmp(name, "Feature"))) {
ALOGW("ignoring %s specified outside of a Type", name);
} else if (!strcmp(name, "Limit")) {
mInitCheck = addLimit(attrs);
} else if (!strcmp(name, "Feature")) {
mInitCheck = addFeature(attrs);
}
break;
}
default:
break;
}
++mDepth;
}
void MediaCodecsXmlParser::endElementHandler(const char *name) {
if (mInitCheck != OK) {
return;
}
switch (mCurrentSection) {
case SECTION_SETTINGS:
{
if (!strcmp(name, "Settings")) {
mCurrentSection = SECTION_TOPLEVEL;
}
break;
}
case SECTION_DECODERS:
{
if (!strcmp(name, "Decoders")) {
mCurrentSection = SECTION_TOPLEVEL;
}
break;
}
case SECTION_ENCODERS:
{
if (!strcmp(name, "Encoders")) {
mCurrentSection = SECTION_TOPLEVEL;
}
break;
}
case SECTION_DECODER_TYPE:
case SECTION_ENCODER_TYPE:
{
if (!strcmp(name, "Type")) {
mCurrentSection =
(mCurrentSection == SECTION_DECODER_TYPE
? SECTION_DECODER : SECTION_ENCODER);
mCurrentType = mCodecInfos[mCurrentName].mTypes.end();
}
break;
}
case SECTION_DECODER:
{
if (!strcmp(name, "MediaCodec")) {
mCurrentSection = SECTION_DECODERS;
mCurrentName.clear();
}
break;
}
case SECTION_ENCODER:
{
if (!strcmp(name, "MediaCodec")) {
mCurrentSection = SECTION_ENCODERS;
mCurrentName.clear();
}
break;
}
case SECTION_INCLUDE:
{
if (!strcmp(name, "Include") && mPastSections.size() > 0) {
mCurrentSection = mPastSections.top();
mPastSections.pop();
}
break;
}
default:
break;
}
--mDepth;
}
status_t MediaCodecsXmlParser::addSettingFromAttributes(const char **attrs) {
const char *name = NULL;
const char *value = NULL;
const char *update = NULL;
size_t i = 0;
while (attrs[i] != NULL) {
if (!strcmp(attrs[i], "name")) {
if (attrs[i + 1] == NULL) {
ALOGE("addSettingFromAttributes: name is null");
return -EINVAL;
}
name = attrs[i + 1];
++i;
} else if (!strcmp(attrs[i], "value")) {
if (attrs[i + 1] == NULL) {
ALOGE("addSettingFromAttributes: value is null");
return -EINVAL;
}
value = attrs[i + 1];
++i;
} else if (!strcmp(attrs[i], "update")) {
if (attrs[i + 1] == NULL) {
ALOGE("addSettingFromAttributes: update is null");
return -EINVAL;
}
update = attrs[i + 1];
++i;
} else {
ALOGE("addSettingFromAttributes: unrecognized attribute: %s", attrs[i]);
return -EINVAL;
}
++i;
}
if (name == NULL || value == NULL) {
ALOGE("addSettingFromAttributes: name or value unspecified");
return -EINVAL;
}
mUpdate = (update != NULL) && ParseBoolean(update);
if (mUpdate != (mGlobalSettings.count(name) > 0)) {
ALOGE("addSettingFromAttributes: updating non-existing setting");
return -EINVAL;
}
mGlobalSettings[name] = value;
return OK;
}
status_t MediaCodecsXmlParser::addMediaCodecFromAttributes(
bool encoder, const char **attrs) {
const char *name = NULL;
const char *type = NULL;
const char *update = NULL;
size_t i = 0;
while (attrs[i] != NULL) {
if (!strcmp(attrs[i], "name")) {
if (attrs[i + 1] == NULL) {
ALOGE("addMediaCodecFromAttributes: name is null");
return -EINVAL;
}
name = attrs[i + 1];
++i;
} else if (!strcmp(attrs[i], "type")) {
if (attrs[i + 1] == NULL) {
ALOGE("addMediaCodecFromAttributes: type is null");
return -EINVAL;
}
type = attrs[i + 1];
++i;
} else if (!strcmp(attrs[i], "update")) {
if (attrs[i + 1] == NULL) {
ALOGE("addMediaCodecFromAttributes: update is null");
return -EINVAL;
}
update = attrs[i + 1];
++i;
} else {
ALOGE("addMediaCodecFromAttributes: unrecognized attribute: %s", attrs[i]);
return -EINVAL;
}
++i;
}
if (name == NULL) {
ALOGE("addMediaCodecFromAttributes: name not found");
return -EINVAL;
}
mUpdate = (update != NULL) && ParseBoolean(update);
if (mUpdate != (mCodecInfos.count(name) > 0)) {
ALOGE("addMediaCodecFromAttributes: updating non-existing codec or vice versa");
return -EINVAL;
}
CodecInfo *info = &mCodecInfos[name];
if (mUpdate) {
// existing codec
mCurrentName = name;
mCurrentType = info->mTypes.begin();
if (type != NULL) {
// existing type
mCurrentType = findTypeInfo(*info, type);
if (mCurrentType == info->mTypes.end()) {
ALOGE("addMediaCodecFromAttributes: updating non-existing type");
return -EINVAL;
}
}
} else {
// new codec
mCurrentName = name;
mQuirks[name].clear();
info->mTypes.clear();
info->mTypes.emplace_back();
mCurrentType = --info->mTypes.end();
mCurrentType->mName = type;
info->mIsEncoder = encoder;
}
return OK;
}
status_t MediaCodecsXmlParser::addQuirk(const char **attrs) {
const char *name = NULL;
size_t i = 0;
while (attrs[i] != NULL) {
if (!strcmp(attrs[i], "name")) {
if (attrs[i + 1] == NULL) {
ALOGE("addQuirk: name is null");
return -EINVAL;
}
name = attrs[i + 1];
++i;
} else {
ALOGE("addQuirk: unrecognized attribute: %s", attrs[i]);
return -EINVAL;
}
++i;
}
if (name == NULL) {
ALOGE("addQuirk: name not found");
return -EINVAL;
}
mQuirks[mCurrentName].emplace_back(name);
return OK;
}
status_t MediaCodecsXmlParser::addTypeFromAttributes(const char **attrs, bool encoder) {
const char *name = NULL;
const char *update = NULL;
size_t i = 0;
while (attrs[i] != NULL) {
if (!strcmp(attrs[i], "name")) {
if (attrs[i + 1] == NULL) {
ALOGE("addTypeFromAttributes: name is null");
return -EINVAL;
}
name = attrs[i + 1];
++i;
} else if (!strcmp(attrs[i], "update")) {
if (attrs[i + 1] == NULL) {
ALOGE("addTypeFromAttributes: update is null");
return -EINVAL;
}
update = attrs[i + 1];
++i;
} else {
ALOGE("addTypeFromAttributes: unrecognized attribute: %s", attrs[i]);
return -EINVAL;
}
++i;
}
if (name == NULL) {
return -EINVAL;
}
CodecInfo *info = &mCodecInfos[mCurrentName];
info->mIsEncoder = encoder;
mCurrentType = findTypeInfo(*info, name);
if (!mUpdate) {
if (mCurrentType != info->mTypes.end()) {
ALOGE("addTypeFromAttributes: re-defining existing type without update");
return -EINVAL;
}
info->mTypes.emplace_back();
mCurrentType = --info->mTypes.end();
} else if (mCurrentType == info->mTypes.end()) {
ALOGE("addTypeFromAttributes: updating non-existing type");
return -EINVAL;
}
return OK;
}
static status_t limitFoundMissingAttr(const AString &name, const char *attr, bool found = true) {
ALOGE("limit '%s' with %s'%s' attribute", name.c_str(),
(found ? "" : "no "), attr);
return -EINVAL;
}
static status_t limitError(const AString &name, const char *msg) {
ALOGE("limit '%s' %s", name.c_str(), msg);
return -EINVAL;
}
static status_t limitInvalidAttr(const AString &name, const char *attr, const AString &value) {
ALOGE("limit '%s' with invalid '%s' attribute (%s)", name.c_str(),
attr, value.c_str());
return -EINVAL;
}
status_t MediaCodecsXmlParser::addLimit(const char **attrs) {
sp<AMessage> msg = new AMessage();
size_t i = 0;
while (attrs[i] != NULL) {
if (attrs[i + 1] == NULL) {
ALOGE("addLimit: limit is not given");
return -EINVAL;
}
// attributes with values
if (!strcmp(attrs[i], "name")
|| !strcmp(attrs[i], "default")
|| !strcmp(attrs[i], "in")
|| !strcmp(attrs[i], "max")
|| !strcmp(attrs[i], "min")
|| !strcmp(attrs[i], "range")
|| !strcmp(attrs[i], "ranges")
|| !strcmp(attrs[i], "scale")
|| !strcmp(attrs[i], "value")) {
msg->setString(attrs[i], attrs[i + 1]);
++i;
} else {
ALOGE("addLimit: unrecognized limit: %s", attrs[i]);
return -EINVAL;
}
++i;
}
AString name;
if (!msg->findString("name", &name)) {
ALOGE("limit with no 'name' attribute");
return -EINVAL;
}
// size, blocks, bitrate, frame-rate, blocks-per-second, aspect-ratio,
// measured-frame-rate, measured-blocks-per-second: range
// quality: range + default + [scale]
// complexity: range + default
bool found;
if (mCurrentType == mCodecInfos[mCurrentName].mTypes.end()) {
ALOGW("ignoring null type");
return OK;
}
if (name == "aspect-ratio" || name == "bitrate" || name == "block-count"
|| name == "blocks-per-second" || name == "complexity"
|| name == "frame-rate" || name == "quality" || name == "size"
|| name == "measured-blocks-per-second" || name.startsWith("measured-frame-rate-")) {
AString min, max;
if (msg->findString("min", &min) && msg->findString("max", &max)) {
min.append("-");
min.append(max);
if (msg->contains("range") || msg->contains("value")) {
return limitError(name, "has 'min' and 'max' as well as 'range' or "
"'value' attributes");
}
msg->setString("range", min);
} else if (msg->contains("min") || msg->contains("max")) {
return limitError(name, "has only 'min' or 'max' attribute");
} else if (msg->findString("value", &max)) {
min = max;
min.append("-");
min.append(max);
if (msg->contains("range")) {
return limitError(name, "has both 'range' and 'value' attributes");
}
msg->setString("range", min);
}
AString range, scale = "linear", def, in_;
if (!msg->findString("range", &range)) {
return limitError(name, "with no 'range', 'value' or 'min'/'max' attributes");
}
if ((name == "quality" || name == "complexity") ^
(found = msg->findString("default", &def))) {
return limitFoundMissingAttr(name, "default", found);
}
if (name != "quality" && msg->findString("scale", &scale)) {
return limitFoundMissingAttr(name, "scale");
}
if ((name == "aspect-ratio") ^ (found = msg->findString("in", &in_))) {
return limitFoundMissingAttr(name, "in", found);
}
if (name == "aspect-ratio") {
if (!(in_ == "pixels") && !(in_ == "blocks")) {
return limitInvalidAttr(name, "in", in_);
}
in_.erase(5, 1); // (pixel|block)-aspect-ratio
in_.append("-");
in_.append(name);
name = in_;
}
if (name == "quality") {
mCurrentType->mDetails["quality-scale"] = scale;
}
if (name == "quality" || name == "complexity") {
AString tag = name;
tag.append("-default");
mCurrentType->mDetails[tag] = def;
}
AString tag = name;
tag.append("-range");
mCurrentType->mDetails[tag] = range;
} else {
AString max, value, ranges;
if (msg->contains("default")) {
return limitFoundMissingAttr(name, "default");
} else if (msg->contains("in")) {
return limitFoundMissingAttr(name, "in");
} else if ((name == "channel-count" || name == "concurrent-instances") ^
(found = msg->findString("max", &max))) {
return limitFoundMissingAttr(name, "max", found);
} else if (msg->contains("min")) {
return limitFoundMissingAttr(name, "min");
} else if (msg->contains("range")) {
return limitFoundMissingAttr(name, "range");
} else if ((name == "sample-rate") ^
(found = msg->findString("ranges", &ranges))) {
return limitFoundMissingAttr(name, "ranges", found);
} else if (msg->contains("scale")) {
return limitFoundMissingAttr(name, "scale");
} else if ((name == "alignment" || name == "block-size") ^
(found = msg->findString("value", &value))) {
return limitFoundMissingAttr(name, "value", found);
}
if (max.size()) {
AString tag = "max-";
tag.append(name);
mCurrentType->mDetails[tag] = max;
} else if (value.size()) {
mCurrentType->mDetails[name] = value;
} else if (ranges.size()) {
AString tag = name;
tag.append("-ranges");
mCurrentType->mDetails[tag] = ranges;
} else {
ALOGW("Ignoring unrecognized limit '%s'", name.c_str());
}
}
return OK;
}
status_t MediaCodecsXmlParser::addFeature(const char **attrs) {
size_t i = 0;
const char *name = NULL;
int32_t optional = -1;
int32_t required = -1;
const char *value = NULL;
while (attrs[i] != NULL) {
if (attrs[i + 1] == NULL) {
ALOGE("addFeature: feature is not given");
return -EINVAL;
}
// attributes with values
if (!strcmp(attrs[i], "name")) {
name = attrs[i + 1];
++i;
} else if (!strcmp(attrs[i], "optional") || !strcmp(attrs[i], "required")) {
int value = (int)ParseBoolean(attrs[i + 1]);
if (!strcmp(attrs[i], "optional")) {
optional = value;
} else {
required = value;
}
++i;
} else if (!strcmp(attrs[i], "value")) {
value = attrs[i + 1];
++i;
} else {
ALOGE("addFeature: unrecognized attribute: %s", attrs[i]);
return -EINVAL;
}
++i;
}
if (name == NULL) {
ALOGE("feature with no 'name' attribute");
return -EINVAL;
}
if (optional == required && optional != -1) {
ALOGE("feature '%s' is both/neither optional and required", name);
return -EINVAL;
}
if (mCurrentType == mCodecInfos[mCurrentName].mTypes.end()) {
ALOGW("ignoring null type");
return OK;
}
if (value != NULL) {
mCurrentType->mStringFeatures[name] = value;
} else {
mCurrentType->mBoolFeatures[name] = (required == 1) || (optional == 0);
}
return OK;
}
void MediaCodecsXmlParser::getGlobalSettings(
std::map<AString, AString> *settings) const {
settings->clear();
settings->insert(mGlobalSettings.begin(), mGlobalSettings.end());
}
status_t MediaCodecsXmlParser::getCodecInfo(const char *name, CodecInfo *info) const {
if (mCodecInfos.count(name) == 0) {
ALOGE("Codec not found with name '%s'", name);
return NAME_NOT_FOUND;
}
*info = mCodecInfos.at(name);
return OK;
}
status_t MediaCodecsXmlParser::getQuirks(const char *name, std::vector<AString> *quirks) const {
if (mQuirks.count(name) == 0) {
ALOGE("Codec not found with name '%s'", name);
return NAME_NOT_FOUND;
}
quirks->clear();
quirks->insert(quirks->end(), mQuirks.at(name).begin(), mQuirks.at(name).end());
return OK;
}
} // namespace android