From 9e6955a19bf77ebf27b770e910efbe1ebf1ceac0 Mon Sep 17 00:00:00 2001 From: Ronghua Wu Date: Thu, 26 Mar 2015 13:52:57 -0700 Subject: media: handle overrides and measure max codec instance. Bug: 19620911 Change-Id: I68d5919284700f37ccc6c6b9f96cd87ccdd40e6a --- media/libmedia/IMediaCodecList.cpp | 28 ++ media/libmedia/MediaCodecInfo.cpp | 11 + media/libstagefright/Android.mk | 1 + media/libstagefright/MediaCodecList.cpp | 192 ++++++++-- media/libstagefright/MediaCodecListOverrides.cpp | 404 +++++++++++++++++++++ media/libstagefright/MediaCodecListOverrides.h | 50 +++ media/libstagefright/tests/Android.mk | 27 ++ .../tests/MediaCodecListOverrides_test.cpp | 316 ++++++++++++++++ 8 files changed, 998 insertions(+), 31 deletions(-) create mode 100644 media/libstagefright/MediaCodecListOverrides.cpp create mode 100644 media/libstagefright/MediaCodecListOverrides.h create mode 100644 media/libstagefright/tests/MediaCodecListOverrides_test.cpp (limited to 'media') diff --git a/media/libmedia/IMediaCodecList.cpp b/media/libmedia/IMediaCodecList.cpp index 80020db..e2df104 100644 --- a/media/libmedia/IMediaCodecList.cpp +++ b/media/libmedia/IMediaCodecList.cpp @@ -30,6 +30,7 @@ enum { CREATE = IBinder::FIRST_CALL_TRANSACTION, COUNT_CODECS, GET_CODEC_INFO, + GET_GLOBAL_SETTINGS, FIND_CODEC_BY_TYPE, FIND_CODEC_BY_NAME, }; @@ -64,6 +65,19 @@ public: } } + virtual const sp getGlobalSettings() const + { + Parcel data, reply; + data.writeInterfaceToken(IMediaCodecList::getInterfaceDescriptor()); + remote()->transact(GET_GLOBAL_SETTINGS, data, &reply); + status_t err = reply.readInt32(); + if (err == OK) { + return AMessage::FromParcel(reply); + } else { + return NULL; + } + } + virtual ssize_t findCodecByType( const char *type, bool encoder, size_t startIndex = 0) const { @@ -125,6 +139,20 @@ status_t BnMediaCodecList::onTransact( } break; + case GET_GLOBAL_SETTINGS: + { + CHECK_INTERFACE(IMediaCodecList, data, reply); + const sp info = getGlobalSettings(); + if (info != NULL) { + reply->writeInt32(OK); + info->writeToParcel(reply); + } else { + reply->writeInt32(-ERANGE); + } + return NO_ERROR; + } + break; + case FIND_CODEC_BY_TYPE: { CHECK_INTERFACE(IMediaCodecList, data, reply); diff --git a/media/libmedia/MediaCodecInfo.cpp b/media/libmedia/MediaCodecInfo.cpp index 7b4c4e2..8d3fa7b 100644 --- a/media/libmedia/MediaCodecInfo.cpp +++ b/media/libmedia/MediaCodecInfo.cpp @@ -206,6 +206,17 @@ status_t MediaCodecInfo::addMime(const char *mime) { return OK; } +status_t MediaCodecInfo::updateMime(const char *mime) { + ssize_t ix = getCapabilityIndex(mime); + if (ix < 0) { + ALOGE("updateMime mime not found %s", mime); + return -EINVAL; + } + + mCurrentCaps = mCaps.valueAt(ix); + return OK; +} + void MediaCodecInfo::removeMime(const char *mime) { ssize_t ix = getCapabilityIndex(mime); if (ix >= 0) { diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index a2cbdaf..b0eeb7f 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -34,6 +34,7 @@ LOCAL_SRC_FILES:= \ MediaClock.cpp \ MediaCodec.cpp \ MediaCodecList.cpp \ + MediaCodecListOverrides.cpp \ MediaCodecSource.cpp \ MediaDefs.cpp \ MediaExtractor.cpp \ diff --git a/media/libstagefright/MediaCodecList.cpp b/media/libstagefright/MediaCodecList.cpp index 3e757c7..26798ae 100644 --- a/media/libstagefright/MediaCodecList.cpp +++ b/media/libstagefright/MediaCodecList.cpp @@ -18,6 +18,8 @@ #define LOG_TAG "MediaCodecList" #include +#include "MediaCodecListOverrides.h" + #include #include @@ -31,6 +33,7 @@ #include #include +#include #include #include @@ -41,6 +44,8 @@ static Mutex sInitMutex; static MediaCodecList *gCodecList = NULL; +static const char *kProfilingResults = "/data/misc/media/media_codecs_profiling_results.xml"; + static bool parseBoolean(const char *s) { if (!strcasecmp(s, "true") || !strcasecmp(s, "yes") || !strcasecmp(s, "y")) { return true; @@ -55,16 +60,42 @@ sp MediaCodecList::sCodecList; // static sp MediaCodecList::getLocalInstance() { - Mutex::Autolock autoLock(sInitMutex); - - if (gCodecList == NULL) { - gCodecList = new MediaCodecList; - if (gCodecList->initCheck() == OK) { - sCodecList = gCodecList; + bool profilingNeeded = false; + KeyedVector updates; + Vector> infos; + + { + Mutex::Autolock autoLock(sInitMutex); + + if (gCodecList == NULL) { + gCodecList = new MediaCodecList; + if (gCodecList->initCheck() == OK) { + sCodecList = gCodecList; + + struct stat s; + if (stat(kProfilingResults, &s) == -1) { + // profiling results doesn't existed + profilingNeeded = true; + for (size_t i = 0; i < gCodecList->countCodecs(); ++i) { + infos.push_back(gCodecList->getCodecInfo(i)); + } + } + } } } - return sCodecList; + if (profilingNeeded) { + profileCodecs(infos, &updates); + } + + { + Mutex::Autolock autoLock(sInitMutex); + if (updates.size() > 0) { + gCodecList->updateDetailsForMultipleCodecs(updates); + } + + return sCodecList; + } } static Mutex sRemoteInitMutex; @@ -103,11 +134,27 @@ sp MediaCodecList::getInstance() { } MediaCodecList::MediaCodecList() - : mInitCheck(NO_INIT) { + : mInitCheck(NO_INIT), + mUpdate(false), + mGlobalSettings(new AMessage()) { parseTopLevelXMLFile("/etc/media_codecs.xml"); + parseTopLevelXMLFile(kProfilingResults, true/* ignore_errors */); } -void MediaCodecList::parseTopLevelXMLFile(const char *codecs_xml) { +void MediaCodecList::updateDetailsForMultipleCodecs( + const KeyedVector& updates) { + if (updates.size() == 0) { + return; + } + + exportResultsToXML(kProfilingResults, updates); + + for (size_t i = 0; i < updates.size(); ++i) { + applyCodecSettings(updates.keyAt(i), updates.valueAt(i), &mCodecInfos); + } +} + +void MediaCodecList::parseTopLevelXMLFile(const char *codecs_xml, bool ignore_errors) { // get href_base char *href_base_end = strrchr(codecs_xml, '/'); if (href_base_end != NULL) { @@ -128,21 +175,16 @@ void MediaCodecList::parseTopLevelXMLFile(const char *codecs_xml) { mOMX.clear(); if (mInitCheck != OK) { + if (ignore_errors) { + mInitCheck = OK; + return; + } mCodecInfos.clear(); return; } - // TODO: parse/create overrides.xml, update mCodecInfos and mSettings with overrides. - for (size_t i = mCodecInfos.size(); i-- > 0;) { const MediaCodecInfo &info = *mCodecInfos.itemAt(i).get(); - for (size_t i = 0; i < info.mCaps.size(); ++i) { - const sp &caps = info.mCaps.valueAt(i); - for (size_t j = 0; j < mSettings.size(); ++j) { - caps->getDetails()->setString(mSettings.keyAt(j).c_str(), mSettings[j].c_str()); - } - } - if (info.mCaps.size() == 0) { // No types supported by this component??? ALOGW("Component %s does not support any type of media?", @@ -186,6 +228,16 @@ void MediaCodecList::parseTopLevelXMLFile(const char *codecs_xml) { } ALOGV(" levels=[%s]", nice.c_str()); } + { + AString quirks; + for (size_t ix = 0; ix < info.mQuirks.size(); ix++) { + if (ix > 0) { + quirks.append(", "); + } + quirks.append(info.mQuirks[ix]); + } + ALOGV(" quirks=[%s]", quirks.c_str()); + } } #endif } @@ -533,20 +585,45 @@ status_t MediaCodecList::addSettingFromAttributes(const char **attrs) { return -EINVAL; } - bool isUpdate = (update != NULL) && parseBoolean(update); - bool isExisted = (mSettings.indexOfKey(name) >= 0); - if (isUpdate != isExisted) { + mUpdate = (update != NULL) && parseBoolean(update); + if (mUpdate != mGlobalSettings->contains(name)) { return -EINVAL; } - mSettings.add(name, value); + mGlobalSettings->setString(name, value); return OK; } +void MediaCodecList::setCurrentCodecInfo(bool encoder, const char *name, const char *type) { + for (size_t i = 0; i < mCodecInfos.size(); ++i) { + if (AString(name) == mCodecInfos[i]->getCodecName()) { + if (mCodecInfos[i]->getCapabilitiesFor(type) == NULL) { + ALOGW("Overrides with an unexpected mime %s", type); + // Create a new MediaCodecInfo (but don't add it to mCodecInfos) to hold the + // overrides we don't want. + mCurrentInfo = new MediaCodecInfo(name, encoder, type); + } else { + mCurrentInfo = mCodecInfos.editItemAt(i); + mCurrentInfo->updateMime(type); // to set the current cap + } + return; + } + } + mCurrentInfo = new MediaCodecInfo(name, encoder, type); + // The next step involves trying to load the codec, which may + // fail. Only list the codec if this succeeds. + // However, keep mCurrentInfo object around until parsing + // of full codec info is completed. + if (initializeCapabilities(type) == OK) { + mCodecInfos.push_back(mCurrentInfo); + } +} + status_t MediaCodecList::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) { @@ -562,6 +639,12 @@ status_t MediaCodecList::addMediaCodecFromAttributes( } type = attrs[i + 1]; ++i; + } else if (!strcmp(attrs[i], "update")) { + if (attrs[i + 1] == NULL) { + return -EINVAL; + } + update = attrs[i + 1]; + ++i; } else { return -EINVAL; } @@ -573,14 +656,39 @@ status_t MediaCodecList::addMediaCodecFromAttributes( return -EINVAL; } - mCurrentInfo = new MediaCodecInfo(name, encoder, type); - // The next step involves trying to load the codec, which may - // fail. Only list the codec if this succeeds. - // However, keep mCurrentInfo object around until parsing - // of full codec info is completed. - if (initializeCapabilities(type) == OK) { - mCodecInfos.push_back(mCurrentInfo); + mUpdate = (update != NULL) && parseBoolean(update); + ssize_t index = -1; + for (size_t i = 0; i < mCodecInfos.size(); ++i) { + if (AString(name) == mCodecInfos[i]->getCodecName()) { + index = i; + } + } + if (mUpdate != (index >= 0)) { + return -EINVAL; + } + + if (index >= 0) { + // existing codec + mCurrentInfo = mCodecInfos.editItemAt(index); + if (type != NULL) { + // existing type + if (mCodecInfos[index]->getCapabilitiesFor(type) == NULL) { + return -EINVAL; + } + mCurrentInfo->updateMime(type); + } + } else { + // new codec + mCurrentInfo = new MediaCodecInfo(name, encoder, type); + // The next step involves trying to load the codec, which may + // fail. Only list the codec if this succeeds. + // However, keep mCurrentInfo object around until parsing + // of full codec info is completed. + if (initializeCapabilities(type) == OK) { + mCodecInfos.push_back(mCurrentInfo); + } } + return OK; } @@ -634,6 +742,7 @@ status_t MediaCodecList::addQuirk(const char **attrs) { status_t MediaCodecList::addTypeFromAttributes(const char **attrs) { const char *name = NULL; + const char *update = NULL; size_t i = 0; while (attrs[i] != NULL) { @@ -643,6 +752,12 @@ status_t MediaCodecList::addTypeFromAttributes(const char **attrs) { } name = attrs[i + 1]; ++i; + } else if (!strcmp(attrs[i], "update")) { + if (attrs[i + 1] == NULL) { + return -EINVAL; + } + update = attrs[i + 1]; + ++i; } else { return -EINVAL; } @@ -654,14 +769,25 @@ status_t MediaCodecList::addTypeFromAttributes(const char **attrs) { return -EINVAL; } - status_t ret = mCurrentInfo->addMime(name); + bool isExistingType = (mCurrentInfo->getCapabilitiesFor(name) != NULL); + if (mUpdate != isExistingType) { + return -EINVAL; + } + + status_t ret; + if (mUpdate) { + ret = mCurrentInfo->updateMime(name); + } else { + ret = mCurrentInfo->addMime(name); + } + if (ret != OK) { return ret; } // The next step involves trying to load the codec, which may // fail. Handle this gracefully (by not reporting such mime). - if (initializeCapabilities(name) != OK) { + if (!mUpdate && initializeCapabilities(name) != OK) { mCurrentInfo->removeMime(name); } return OK; @@ -933,4 +1059,8 @@ size_t MediaCodecList::countCodecs() const { return mCodecInfos.size(); } +const sp MediaCodecList::getGlobalSettings() const { + return mGlobalSettings; +} + } // namespace android diff --git a/media/libstagefright/MediaCodecListOverrides.cpp b/media/libstagefright/MediaCodecListOverrides.cpp new file mode 100644 index 0000000..3c54f34 --- /dev/null +++ b/media/libstagefright/MediaCodecListOverrides.cpp @@ -0,0 +1,404 @@ +/* + * Copyright 2015 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 "MediaCodecListOverrides" +#include + +#include "MediaCodecListOverrides.h" + +#include +#include +#include +#include + +#include +#include + +namespace android { + +// a limit to avoid allocating unreasonable number of codec instances in the measurement. +// this should be in sync with the MAX_SUPPORTED_INSTANCES defined in MediaCodecInfo.java. +static const int kMaxInstances = 32; + +// TODO: move MediaCodecInfo to C++. Until then, some temp methods to parse out info. +static bool getMeasureSize(sp caps, int32_t *width, int32_t *height) { + AString sizeRange; + if (!caps->getDetails()->findString("size-range", &sizeRange)) { + return false; + } + AString minSize; + AString maxSize; + if (!splitString(sizeRange, "-", &minSize, &maxSize)) { + return false; + } + AString sWidth; + AString sHeight; + if (!splitString(minSize, "x", &sWidth, &sHeight)) { + if (!splitString(minSize, "*", &sWidth, &sHeight)) { + return false; + } + } + + *width = strtol(sWidth.c_str(), NULL, 10); + *height = strtol(sHeight.c_str(), NULL, 10); + return (*width > 0) && (*height > 0); +} + +static void getMeasureBitrate(sp caps, int32_t *bitrate) { + // Until have native MediaCodecInfo, we cannot get bitrates based on profile/levels. + // We use 200000 as default value for our measurement. + *bitrate = 200000; + AString bitrateRange; + if (!caps->getDetails()->findString("bitrate-range", &bitrateRange)) { + return; + } + AString minBitrate; + AString maxBitrate; + if (!splitString(bitrateRange, "-", &minBitrate, &maxBitrate)) { + return; + } + + *bitrate = strtol(minBitrate.c_str(), NULL, 10); +} + +static sp getMeasureFormat( + bool isEncoder, AString mime, sp caps) { + sp format = new AMessage(); + format->setString("mime", mime); + + if (isEncoder) { + int32_t bitrate = 0; + getMeasureBitrate(caps, &bitrate); + format->setInt32("bitrate", bitrate); + } + + if (mime.startsWith("video/")) { + int32_t width = 0; + int32_t height = 0; + if (!getMeasureSize(caps, &width, &height)) { + return NULL; + } + format->setInt32("width", width); + format->setInt32("height", height); + + Vector colorFormats; + caps->getSupportedColorFormats(&colorFormats); + if (colorFormats.size() == 0) { + return NULL; + } + format->setInt32("color-format", colorFormats[0]); + + format->setFloat("frame-rate", 10.0); + format->setInt32("i-frame-interval", 10); + } else { + // TODO: profile hw audio + return NULL; + } + + return format; +} + +static size_t doProfileCodecs( + bool isEncoder, AString name, AString mime, sp caps) { + sp format = getMeasureFormat(isEncoder, mime, caps); + if (format == NULL) { + return 0; + } + if (isEncoder) { + format->setInt32("encoder", 1); + } + ALOGV("doProfileCodecs %s %s %s %s", + name.c_str(), mime.c_str(), isEncoder ? "encoder" : "decoder", + format->debugString().c_str()); + + status_t err = OK; + Vector> codecs; + while (err == OK && codecs.size() < kMaxInstances) { + sp looper = new ALooper; + looper->setName("MediaCodec_looper"); + ALOGV("doProfileCodecs for codec #%u", codecs.size()); + ALOGV("doProfileCodecs start looper"); + looper->start( + false /* runOnCallingThread */, false /* canCallJava */, ANDROID_PRIORITY_AUDIO); + ALOGV("doProfileCodecs CreateByComponentName"); + sp codec = MediaCodec::CreateByComponentName(looper, name.c_str(), &err); + if (err != OK) { + ALOGV("Failed to create codec: %s", name.c_str()); + break; + } + const sp nativeWindow; + const sp crypto; + uint32_t flags = 0; + ALOGV("doProfileCodecs configure"); + err = codec->configure(format, nativeWindow, crypto, flags); + if (err != OK) { + ALOGV("Failed to configure codec: %s with mime: %s", name.c_str(), mime.c_str()); + codec->release(); + break; + } + ALOGV("doProfileCodecs start"); + err = codec->start(); + if (err != OK) { + ALOGV("Failed to start codec: %s with mime: %s", name.c_str(), mime.c_str()); + codec->release(); + break; + } + codecs.push_back(codec); + } + + for (size_t i = 0; i < codecs.size(); ++i) { + ALOGV("doProfileCodecs release %s", name.c_str()); + err = codecs[i]->release(); + if (err != OK) { + ALOGE("Failed to release codec: %s with mime: %s", name.c_str(), mime.c_str()); + } + } + + return codecs.size(); +} + +static void printLongString(const char *buf, size_t size) { + AString print; + const char *start = buf; + size_t len; + size_t totalLen = size; + while (totalLen > 0) { + len = (totalLen > 500) ? 500 : totalLen; + print.setTo(start, len); + ALOGV("%s", print.c_str()); + totalLen -= len; + start += len; + } +} + +bool splitString(const AString &s, const AString &delimiter, AString *s1, AString *s2) { + ssize_t pos = s.find(delimiter.c_str()); + if (pos < 0) { + return false; + } + *s1 = AString(s, 0, pos); + *s2 = AString(s, pos + 1, s.size() - pos - 1); + return true; +} + +bool splitString( + const AString &s, const AString &delimiter, AString *s1, AString *s2, AString *s3) { + AString temp; + if (!splitString(s, delimiter, s1, &temp)) { + return false; + } + if (!splitString(temp, delimiter, s2, s3)) { + return false; + } + return true; +} + +void profileCodecs( + const Vector> &infos, + KeyedVector *results, + bool forceToMeasure) { + KeyedVector> codecsNeedMeasure; + for (size_t i = 0; i < infos.size(); ++i) { + const sp info = infos[i]; + AString name = info->getCodecName(); + if (name.startsWith("OMX.google.") || + // TODO: reenable below codecs once fixed + name == "OMX.Intel.VideoDecoder.VP9.hybrid") { + continue; + } + + Vector mimes; + info->getSupportedMimes(&mimes); + for (size_t i = 0; i < mimes.size(); ++i) { + const sp &caps = + info->getCapabilitiesFor(mimes[i].c_str()); + if (!forceToMeasure && caps->getDetails()->contains("max-supported-instances")) { + continue; + } + + size_t max = doProfileCodecs(info->isEncoder(), name, mimes[i], caps); + if (max > 0) { + CodecSettings settings; + char maxStr[32]; + sprintf(maxStr, "%u", max); + settings.add("max-supported-instances", maxStr); + + AString key = name; + key.append(" "); + key.append(mimes[i]); + key.append(" "); + key.append(info->isEncoder() ? "encoder" : "decoder"); + results->add(key, settings); + } + } + } +} + +void applyCodecSettings( + const AString& codecInfo, + const CodecSettings &settings, + Vector> *infos) { + AString name; + AString mime; + AString type; + if (!splitString(codecInfo, " ", &name, &mime, &type)) { + return; + } + + for (size_t i = 0; i < infos->size(); ++i) { + const sp &info = infos->itemAt(i); + if (name != info->getCodecName()) { + continue; + } + + Vector mimes; + info->getSupportedMimes(&mimes); + for (size_t j = 0; j < mimes.size(); ++j) { + if (mimes[j] != mime) { + continue; + } + const sp &caps = info->getCapabilitiesFor(mime.c_str()); + for (size_t k = 0; k < settings.size(); ++k) { + caps->getDetails()->setString( + settings.keyAt(k).c_str(), settings.valueAt(k).c_str()); + } + } + } +} + +void exportResultsToXML(const char *fileName, const KeyedVector& results) { +#if LOG_NDEBUG == 0 + ALOGE("measurement results"); + for (size_t i = 0; i < results.size(); ++i) { + ALOGE("key %s", results.keyAt(i).c_str()); + const CodecSettings &settings = results.valueAt(i); + for (size_t j = 0; j < settings.size(); ++j) { + ALOGE("name %s value %s", settings.keyAt(j).c_str(), settings.valueAt(j).c_str()); + } + } +#endif + + AString overrides; + FILE *f = fopen(fileName, "rb"); + if (f != NULL) { + fseek(f, 0, SEEK_END); + long size = ftell(f); + rewind(f); + + char *buf = (char *)malloc(size); + if (fread(buf, size, 1, f) == 1) { + overrides.setTo(buf, size); +#if LOG_NDEBUG == 0 + ALOGV("Existing overrides:"); + printLongString(buf, size); +#endif + } else { + ALOGE("Failed to read %s", fileName); + } + fclose(f); + free(buf); + } + + for (size_t i = 0; i < results.size(); ++i) { + AString name; + AString mime; + AString type; + if (!splitString(results.keyAt(i), " ", &name, &mime, &type)) { + continue; + } + name = AStringPrintf("\"%s\"", name.c_str()); + mime = AStringPrintf("\"%s\"", mime.c_str()); + ALOGV("name(%s) mime(%s) type(%s)", name.c_str(), mime.c_str(), type.c_str()); + ssize_t posCodec = overrides.find(name.c_str()); + size_t posInsert = 0; + if (posCodec < 0) { + AString encodersDecoders = (type == "encoder") ? "" : ""; + AString encodersDecodersEnd = (type == "encoder") ? "" : ""; + ssize_t posEncodersDecoders = overrides.find(encodersDecoders.c_str()); + if (posEncodersDecoders < 0) { + AString mediaCodecs = ""; + ssize_t posMediaCodec = overrides.find(mediaCodecs.c_str()); + if (posMediaCodec < 0) { + posMediaCodec = overrides.size(); + overrides.insert("\n\n\n", posMediaCodec); + posMediaCodec = overrides.find(mediaCodecs.c_str(), posMediaCodec); + } + posEncodersDecoders = posMediaCodec + mediaCodecs.size(); + AString codecs = AStringPrintf( + "\n %s\n %s", encodersDecoders.c_str(), encodersDecodersEnd.c_str()); + overrides.insert(codecs.c_str(), posEncodersDecoders); + posEncodersDecoders = overrides.find(encodersDecoders.c_str(), posEncodersDecoders); + } + posCodec = posEncodersDecoders + encodersDecoders.size(); + AString codec = AStringPrintf( + "\n \n ", + name.c_str(), + mime.c_str()); + overrides.insert(codec.c_str(), posCodec); + posCodec = overrides.find(name.c_str()); + } + + // insert to existing entry + ssize_t posMime = overrides.find(mime.c_str(), posCodec); + ssize_t posEnd = overrides.find(">", posCodec); + if (posEnd < 0) { + ALOGE("Format error in overrides file."); + return; + } + if (posMime < 0 || posMime > posEnd) { + // new mime for an existing component + AString codecEnd = ""; + posInsert = overrides.find(codecEnd.c_str(), posCodec) + codecEnd.size(); + AString codec = AStringPrintf( + "\n \n ", + name.c_str(), + mime.c_str()); + overrides.insert(codec.c_str(), posInsert); + posInsert = overrides.find(">", posInsert) + 1; + } else { + posInsert = posEnd + 1; + } + + CodecSettings settings = results.valueAt(i); + for (size_t i = 0; i < settings.size(); ++i) { + // WARNING: we assume all the settings are "Limit". Currently we have only one type + // of setting in this case, which is "max-supported-instances". + AString strInsert = AStringPrintf( + "\n ", + settings.keyAt(i).c_str(), + settings.valueAt(i).c_str()); + overrides.insert(strInsert, posInsert); + } + } + +#if LOG_NDEBUG == 0 + ALOGV("New overrides:"); + printLongString(overrides.c_str(), overrides.size()); +#endif + + f = fopen(fileName, "wb"); + if (f == NULL) { + ALOGE("Failed to open %s for writing.", fileName); + return; + } + if (fwrite(overrides.c_str(), 1, overrides.size(), f) != overrides.size()) { + ALOGE("Failed to write to %s.", fileName); + } + fclose(f); +} + +} // namespace android diff --git a/media/libstagefright/MediaCodecListOverrides.h b/media/libstagefright/MediaCodecListOverrides.h new file mode 100644 index 0000000..f97ce63 --- /dev/null +++ b/media/libstagefright/MediaCodecListOverrides.h @@ -0,0 +1,50 @@ +/* + * Copyright 2015 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 MEDIA_CODEC_LIST_OVERRIDES_H_ + +#define MEDIA_CODEC_LIST_OVERRIDES_H_ + +#include +#include + +#include +#include + +namespace android { + +class MediaCodecInfo; + +bool splitString(const AString &s, const AString &delimiter, AString *s1, AString *s2); + +bool splitString( + const AString &s, const AString &delimiter, AString *s1, AString *s2, AString *s3); + +void profileCodecs( + const Vector> &infos, + KeyedVector *results, + bool forceToMeasure = false); // forceToMeasure is mainly for testing + +void applyCodecSettings( + const AString& codecInfo, + const CodecSettings &settings, + Vector> *infos); + +void exportResultsToXML(const char *fileName, const KeyedVector& results); + +} // namespace android + +#endif // MEDIA_CODEC_LIST_OVERRIDES_H_ diff --git a/media/libstagefright/tests/Android.mk b/media/libstagefright/tests/Android.mk index 8d6ff5b..51e1c78 100644 --- a/media/libstagefright/tests/Android.mk +++ b/media/libstagefright/tests/Android.mk @@ -62,6 +62,33 @@ LOCAL_C_INCLUDES := \ include $(BUILD_NATIVE_TEST) +include $(CLEAR_VARS) +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk + +LOCAL_MODULE := MediaCodecListOverrides_test + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := \ + MediaCodecListOverrides_test.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libmedia \ + libstagefright \ + libstagefright_foundation \ + libstagefright_omx \ + libutils \ + liblog + +LOCAL_C_INCLUDES := \ + frameworks/av/media/libstagefright \ + frameworks/av/media/libstagefright/include \ + frameworks/native/include/media/openmax \ + +LOCAL_32_BIT_ONLY := true + +include $(BUILD_NATIVE_TEST) + # Include subdirectory makefiles # ============================================================ diff --git a/media/libstagefright/tests/MediaCodecListOverrides_test.cpp b/media/libstagefright/tests/MediaCodecListOverrides_test.cpp new file mode 100644 index 0000000..cacaa84 --- /dev/null +++ b/media/libstagefright/tests/MediaCodecListOverrides_test.cpp @@ -0,0 +1,316 @@ +/* + * Copyright 2015 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 "MediaCodecListOverrides_test" +#include + +#include + +#include "MediaCodecListOverrides.h" + +#include +#include +#include + +namespace android { + +static const char kTestOverridesStr[] = +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n"; + +static const char kTestOverridesStrNew1[] = +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n"; + +static const char kTestOverridesStrNew2[] = +"\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n"; + +class MediaCodecListOverridesTest : public ::testing::Test { +public: + MediaCodecListOverridesTest() {} + + void verifyOverrides(const KeyedVector &overrides) { + EXPECT_EQ(3u, overrides.size()); + + EXPECT_TRUE(overrides.keyAt(0) == "OMX.qcom.video.decoder.avc video/avc decoder"); + const CodecSettings &settings0 = overrides.valueAt(0); + EXPECT_EQ(1u, settings0.size()); + EXPECT_TRUE(settings0.keyAt(0) == "max-supported-instances"); + EXPECT_TRUE(settings0.valueAt(0) == "4"); + + EXPECT_TRUE(overrides.keyAt(1) == "OMX.qcom.video.encoder.avc video/avc encoder"); + const CodecSettings &settings1 = overrides.valueAt(1); + EXPECT_EQ(1u, settings1.size()); + EXPECT_TRUE(settings1.keyAt(0) == "max-supported-instances"); + EXPECT_TRUE(settings1.valueAt(0) == "3"); + + EXPECT_TRUE(overrides.keyAt(2) == "global"); + const CodecSettings &settings2 = overrides.valueAt(2); + EXPECT_EQ(3u, settings2.size()); + EXPECT_TRUE(settings2.keyAt(0) == "max-max-supported-instances"); + EXPECT_TRUE(settings2.valueAt(0) == "8"); + EXPECT_TRUE(settings2.keyAt(1) == "supports-multiple-secure-codecs"); + EXPECT_TRUE(settings2.valueAt(1) == "false"); + EXPECT_TRUE(settings2.keyAt(2) == "supports-secure-with-non-secure-codec"); + EXPECT_TRUE(settings2.valueAt(2) == "true"); + } + + void verifySetting(const sp &details, const char *name, const char *value) { + AString value1; + EXPECT_TRUE(details->findString(name, &value1)); + EXPECT_TRUE(value1 == value); + } + + void createTestInfos(Vector> *infos) { + const char *name = "OMX.qcom.video.decoder.avc"; + const bool encoder = false; + const char *mime = "video/avc"; + sp info = new MediaCodecInfo(name, encoder, mime); + infos->push_back(info); + const sp caps = info->getCapabilitiesFor(mime); + const sp details = caps->getDetails(); + details->setString("cap1", "value1"); + details->setString("max-max-supported-instances", "16"); + + info = new MediaCodecInfo("anothercodec", true, "anothermime"); + infos->push_back(info); + } + + void addMaxInstancesSetting( + const AString &key, + const AString &value, + KeyedVector *results) { + CodecSettings settings; + settings.add("max-supported-instances", value); + results->add(key, settings); + } + + void exportTestResultsToXML(const char *fileName) { + KeyedVector r; + addMaxInstancesSetting("OMX.qcom.video.decoder.avc.secure video/avc decoder", "1", &r); + addMaxInstancesSetting("OMX.qcom.video.decoder.h263 video/3gpp decoder", "4", &r); + addMaxInstancesSetting("OMX.qcom.video.decoder.mpeg2 video/mpeg2 decoder", "3", &r); + addMaxInstancesSetting("OMX.qcom.video.decoder.mpeg4 video/mp4v-es decoder", "3", &r); + addMaxInstancesSetting("OMX.qcom.video.encoder.avc video/avc encoder", "4", &r); + addMaxInstancesSetting("OMX.qcom.video.encoder.mpeg4 video/mp4v-es encoder", "4", &r); + + exportResultsToXML(fileName, r); + } +}; + +TEST_F(MediaCodecListOverridesTest, splitString) { + AString s = "abc123"; + AString delimiter = " "; + AString s1; + AString s2; + EXPECT_FALSE(splitString(s, delimiter, &s1, &s2)); + s = "abc 123"; + EXPECT_TRUE(splitString(s, delimiter, &s1, &s2)); + EXPECT_TRUE(s1 == "abc"); + EXPECT_TRUE(s2 == "123"); + + s = "abc123xyz"; + delimiter = ","; + AString s3; + EXPECT_FALSE(splitString(s, delimiter, &s1, &s2, &s3)); + s = "abc,123xyz"; + EXPECT_FALSE(splitString(s, delimiter, &s1, &s2, &s3)); + s = "abc,123,xyz"; + EXPECT_TRUE(splitString(s, delimiter, &s1, &s2, &s3)); + EXPECT_TRUE(s1 == "abc"); + EXPECT_TRUE(s2 == "123" ); + EXPECT_TRUE(s3 == "xyz"); +} + +// TODO: the codec component never returns OMX_EventCmdComplete in unit test. +TEST_F(MediaCodecListOverridesTest, DISABLED_profileCodecs) { + sp list = MediaCodecList::getInstance(); + Vector> infos; + for (size_t i = 0; i < list->countCodecs(); ++i) { + infos.push_back(list->getCodecInfo(i)); + } + KeyedVector results; + profileCodecs(infos, &results, true /* forceToMeasure */); + EXPECT_LT(0u, results.size()); + for (size_t i = 0; i < results.size(); ++i) { + AString key = results.keyAt(i); + CodecSettings settings = results.valueAt(i); + EXPECT_EQ(1u, settings.size()); + EXPECT_TRUE(settings.keyAt(0) == "max-supported-instances"); + AString valueS = settings.valueAt(0); + int32_t value = strtol(valueS.c_str(), NULL, 10); + EXPECT_LT(0, value); + ALOGV("profileCodecs results %s %s", key.c_str(), valueS.c_str()); + } +} + +TEST_F(MediaCodecListOverridesTest, applyCodecSettings) { + AString codecInfo = "OMX.qcom.video.decoder.avc video/avc decoder"; + Vector> infos; + createTestInfos(&infos); + CodecSettings settings; + settings.add("max-supported-instances", "3"); + settings.add("max-max-supported-instances", "8"); + applyCodecSettings(codecInfo, settings, &infos); + + EXPECT_EQ(2u, infos.size()); + EXPECT_TRUE(AString(infos[0]->getCodecName()) == "OMX.qcom.video.decoder.avc"); + const sp details = infos[0]->getCapabilitiesFor("video/avc")->getDetails(); + verifySetting(details, "max-supported-instances", "3"); + verifySetting(details, "max-max-supported-instances", "8"); + + EXPECT_TRUE(AString(infos[1]->getCodecName()) == "anothercodec"); + EXPECT_EQ(0u, infos[1]->getCapabilitiesFor("anothermime")->getDetails()->countEntries()); +} + +TEST_F(MediaCodecListOverridesTest, exportResultsToExistingFile) { + const char *fileName = "/sdcard/mediacodec_list_overrides_test.xml"; + remove(fileName); + + FILE *f = fopen(fileName, "wb"); + if (f == NULL) { + ALOGW("Failed to open %s for writing.", fileName); + return; + } + EXPECT_EQ( + strlen(kTestOverridesStr), + fwrite(kTestOverridesStr, 1, strlen(kTestOverridesStr), f)); + fclose(f); + + exportTestResultsToXML(fileName); + + // verify + AString overrides; + f = fopen(fileName, "rb"); + ASSERT_TRUE(f != NULL); + fseek(f, 0, SEEK_END); + long size = ftell(f); + rewind(f); + + char *buf = (char *)malloc(size); + EXPECT_EQ(1, fread(buf, size, 1, f)); + overrides.setTo(buf, size); + fclose(f); + free(buf); + + EXPECT_TRUE(overrides == kTestOverridesStrNew1); + + remove(fileName); +} + +TEST_F(MediaCodecListOverridesTest, exportResultsToEmptyFile) { + const char *fileName = "/sdcard/mediacodec_list_overrides_test.xml"; + remove(fileName); + + exportTestResultsToXML(fileName); + + // verify + AString overrides; + FILE *f = fopen(fileName, "rb"); + ASSERT_TRUE(f != NULL); + fseek(f, 0, SEEK_END); + long size = ftell(f); + rewind(f); + + char *buf = (char *)malloc(size); + EXPECT_EQ(1, fread(buf, size, 1, f)); + overrides.setTo(buf, size); + fclose(f); + free(buf); + + EXPECT_TRUE(overrides == kTestOverridesStrNew2); + + remove(fileName); +} + +} // namespace android -- cgit v1.1