diff options
-rw-r--r-- | include/media/IMediaCodecList.h | 4 | ||||
-rw-r--r-- | include/media/MediaCodecInfo.h | 4 | ||||
-rw-r--r-- | include/media/stagefright/MediaCodecList.h | 14 | ||||
-rw-r--r-- | media/libmedia/IMediaCodecList.cpp | 28 | ||||
-rw-r--r-- | media/libmedia/MediaCodecInfo.cpp | 11 | ||||
-rw-r--r-- | media/libstagefright/Android.mk | 1 | ||||
-rw-r--r-- | media/libstagefright/MediaCodecList.cpp | 192 | ||||
-rw-r--r-- | media/libstagefright/MediaCodecListOverrides.cpp | 404 | ||||
-rw-r--r-- | media/libstagefright/MediaCodecListOverrides.h | 50 | ||||
-rw-r--r-- | media/libstagefright/tests/Android.mk | 27 | ||||
-rw-r--r-- | media/libstagefright/tests/MediaCodecListOverrides_test.cpp | 316 |
11 files changed, 1018 insertions, 33 deletions
diff --git a/include/media/IMediaCodecList.h b/include/media/IMediaCodecList.h index e93ea8b..12b52d7 100644 --- a/include/media/IMediaCodecList.h +++ b/include/media/IMediaCodecList.h @@ -21,6 +21,8 @@ #include <binder/IInterface.h> #include <binder/Parcel.h> +#include <media/stagefright/foundation/AMessage.h> + namespace android { struct MediaCodecInfo; @@ -33,6 +35,8 @@ public: virtual size_t countCodecs() const = 0; virtual sp<MediaCodecInfo> getCodecInfo(size_t index) const = 0; + virtual const sp<AMessage> getGlobalSettings() const = 0; + virtual ssize_t findCodecByType( const char *type, bool encoder, size_t startIndex = 0) const = 0; diff --git a/include/media/MediaCodecInfo.h b/include/media/MediaCodecInfo.h index cd56adb..895a13a 100644 --- a/include/media/MediaCodecInfo.h +++ b/include/media/MediaCodecInfo.h @@ -35,6 +35,8 @@ struct AMessage; struct Parcel; struct CodecCapabilities; +typedef KeyedVector<AString, AString> CodecSettings; + struct MediaCodecInfo : public RefBase { struct ProfileLevel { uint32_t mProfile; @@ -104,6 +106,7 @@ private: MediaCodecInfo(AString name, bool encoder, const char *mime); void addQuirk(const char *name); status_t addMime(const char *mime); + status_t updateMime(const char *mime); status_t initializeCapabilities(const CodecCapabilities &caps); void addDetail(const AString &key, const AString &value); void addFeature(const AString &key, int32_t value); @@ -114,6 +117,7 @@ private: DISALLOW_EVIL_CONSTRUCTORS(MediaCodecInfo); friend class MediaCodecList; + friend class MediaCodecListOverridesTest; }; } // namespace android diff --git a/include/media/stagefright/MediaCodecList.h b/include/media/stagefright/MediaCodecList.h index 53f3095..9d1d675 100644 --- a/include/media/stagefright/MediaCodecList.h +++ b/include/media/stagefright/MediaCodecList.h @@ -48,9 +48,14 @@ struct MediaCodecList : public BnMediaCodecList { return mCodecInfos.itemAt(index); } + virtual const sp<AMessage> getGlobalSettings() const; + // to be used by MediaPlayerService alone static sp<IMediaCodecList> getLocalInstance(); + // only to be used in getLocalInstance + void updateDetailsForMultipleCodecs(const KeyedVector<AString, CodecSettings>& updates); + private: class BinderDeathObserver : public IBinder::DeathRecipient { void binderDied(const wp<IBinder> &the_late_who __unused); @@ -75,11 +80,14 @@ private: status_t mInitCheck; Section mCurrentSection; + bool mUpdate; Vector<Section> mPastSections; int32_t mDepth; AString mHrefBase; - KeyedVector<AString, AString> mSettings; + sp<AMessage> mGlobalSettings; + KeyedVector<AString, CodecSettings> mOverrides; + Vector<sp<MediaCodecInfo> > mCodecInfos; sp<MediaCodecInfo> mCurrentInfo; sp<IOMX> mOMX; @@ -89,7 +97,7 @@ private: status_t initCheck() const; void parseXMLFile(const char *path); - void parseTopLevelXMLFile(const char *path); + void parseTopLevelXMLFile(const char *path, bool ignore_errors = false); static void StartElementHandlerWrapper( void *me, const char *name, const char **attrs); @@ -104,6 +112,8 @@ private: status_t addMediaCodecFromAttributes(bool encoder, const char **attrs); void addMediaCodec(bool encoder, const char *name, const char *type = NULL); + void setCurrentCodecInfo(bool encoder, const char *name, const char *type); + status_t addQuirk(const char **attrs); status_t addTypeFromAttributes(const char **attrs); status_t addLimit(const char **attrs); 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<AMessage> 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<AMessage> 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 <utils/Log.h> +#include "MediaCodecListOverrides.h" + #include <binder/IServiceManager.h> #include <media/IMediaCodecList.h> @@ -31,6 +33,7 @@ #include <media/stagefright/OMXClient.h> #include <media/stagefright/OMXCodec.h> +#include <sys/stat.h> #include <utils/threads.h> #include <libexpat/expat.h> @@ -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<IMediaCodecList> MediaCodecList::sCodecList; // static sp<IMediaCodecList> MediaCodecList::getLocalInstance() { - Mutex::Autolock autoLock(sInitMutex); - - if (gCodecList == NULL) { - gCodecList = new MediaCodecList; - if (gCodecList->initCheck() == OK) { - sCodecList = gCodecList; + bool profilingNeeded = false; + KeyedVector<AString, CodecSettings> updates; + Vector<sp<MediaCodecInfo>> 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<IMediaCodecList> 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<AString, CodecSettings>& 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<MediaCodecInfo::Capabilities> &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<AMessage> 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 <utils/Log.h> + +#include "MediaCodecListOverrides.h" + +#include <gui/Surface.h> +#include <media/ICrypto.h> +#include <media/IMediaCodecList.h> +#include <media/MediaCodecInfo.h> + +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaCodec.h> + +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<MediaCodecInfo::Capabilities> 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<MediaCodecInfo::Capabilities> 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<AMessage> getMeasureFormat( + bool isEncoder, AString mime, sp<MediaCodecInfo::Capabilities> caps) { + sp<AMessage> 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<uint32_t> 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<MediaCodecInfo::Capabilities> caps) { + sp<AMessage> 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<sp<MediaCodec>> codecs; + while (err == OK && codecs.size() < kMaxInstances) { + sp<ALooper> 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<MediaCodec> codec = MediaCodec::CreateByComponentName(looper, name.c_str(), &err); + if (err != OK) { + ALOGV("Failed to create codec: %s", name.c_str()); + break; + } + const sp<Surface> nativeWindow; + const sp<ICrypto> 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<sp<MediaCodecInfo>> &infos, + KeyedVector<AString, CodecSettings> *results, + bool forceToMeasure) { + KeyedVector<AString, sp<MediaCodecInfo::Capabilities>> codecsNeedMeasure; + for (size_t i = 0; i < infos.size(); ++i) { + const sp<MediaCodecInfo> 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<AString> mimes; + info->getSupportedMimes(&mimes); + for (size_t i = 0; i < mimes.size(); ++i) { + const sp<MediaCodecInfo::Capabilities> &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<sp<MediaCodecInfo>> *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<MediaCodecInfo> &info = infos->itemAt(i); + if (name != info->getCodecName()) { + continue; + } + + Vector<AString> mimes; + info->getSupportedMimes(&mimes); + for (size_t j = 0; j < mimes.size(); ++j) { + if (mimes[j] != mime) { + continue; + } + const sp<MediaCodecInfo::Capabilities> &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<AString, CodecSettings>& 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") ? "<Encoders>" : "<Decoders>"; + AString encodersDecodersEnd = (type == "encoder") ? "</Encoders>" : "</Decoders>"; + ssize_t posEncodersDecoders = overrides.find(encodersDecoders.c_str()); + if (posEncodersDecoders < 0) { + AString mediaCodecs = "<MediaCodecs>"; + ssize_t posMediaCodec = overrides.find(mediaCodecs.c_str()); + if (posMediaCodec < 0) { + posMediaCodec = overrides.size(); + overrides.insert("\n<MediaCodecs>\n</MediaCodecs>\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 <MediaCodec name=%s type=%s update=\"true\" >\n </MediaCodec>", + 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 = "</MediaCodec>"; + posInsert = overrides.find(codecEnd.c_str(), posCodec) + codecEnd.size(); + AString codec = AStringPrintf( + "\n <MediaCodec name=%s type=%s update=\"true\" >\n </MediaCodec>", + 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 <Limit name=\"%s\" value=\"%s\" />", + 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 <media/MediaCodecInfo.h> +#include <media/stagefright/foundation/AString.h> + +#include <utils/StrongPointer.h> +#include <utils/KeyedVector.h> + +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<sp<MediaCodecInfo>> &infos, + KeyedVector<AString, CodecSettings> *results, + bool forceToMeasure = false); // forceToMeasure is mainly for testing + +void applyCodecSettings( + const AString& codecInfo, + const CodecSettings &settings, + Vector<sp<MediaCodecInfo>> *infos); + +void exportResultsToXML(const char *fileName, const KeyedVector<AString, CodecSettings>& 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 <utils/Log.h> + +#include <gtest/gtest.h> + +#include "MediaCodecListOverrides.h" + +#include <media/MediaCodecInfo.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaCodecList.h> + +namespace android { + +static const char kTestOverridesStr[] = +"<MediaCodecs>\n" +" <Settings>\n" +" <Setting name=\"max-max-supported-instances\" value=\"8\" update=\"true\" />\n" +" </Settings>\n" +" <Encoders>\n" +" <MediaCodec name=\"OMX.qcom.video.encoder.mpeg4\" type=\"video/mp4v-es\" update=\"true\" >\n" +" <Quirk name=\"requires-allocate-on-input-ports\" />\n" +" <Limit name=\"bitrate\" range=\"1-20000000\" />\n" +" <Feature name=\"can-swap-width-height\" />\n" +" </MediaCodec>\n" +" </Encoders>\n" +" <Decoders>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.avc\" type=\"video/avc\" update=\"true\" >\n" +" <Quirk name=\"requires-allocate-on-input-ports\" />\n" +" <Limit name=\"size\" min=\"64x64\" max=\"1920x1088\" />\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.mpeg2\" type=\"different_mime\" update=\"true\" >\n" +" </MediaCodec>\n" +" </Decoders>\n" +"</MediaCodecs>\n"; + +static const char kTestOverridesStrNew1[] = +"<MediaCodecs>\n" +" <Settings>\n" +" <Setting name=\"max-max-supported-instances\" value=\"8\" update=\"true\" />\n" +" </Settings>\n" +" <Encoders>\n" +" <MediaCodec name=\"OMX.qcom.video.encoder.avc\" type=\"video/avc\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"4\" />\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.encoder.mpeg4\" type=\"video/mp4v-es\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"4\" />\n" +" <Quirk name=\"requires-allocate-on-input-ports\" />\n" +" <Limit name=\"bitrate\" range=\"1-20000000\" />\n" +" <Feature name=\"can-swap-width-height\" />\n" +" </MediaCodec>\n" +" </Encoders>\n" +" <Decoders>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.mpeg4\" type=\"video/mp4v-es\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"3\" />\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.h263\" type=\"video/3gpp\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"4\" />\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.avc.secure\" type=\"video/avc\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"1\" />\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.avc\" type=\"video/avc\" update=\"true\" >\n" +" <Quirk name=\"requires-allocate-on-input-ports\" />\n" +" <Limit name=\"size\" min=\"64x64\" max=\"1920x1088\" />\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.mpeg2\" type=\"different_mime\" update=\"true\" >\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.mpeg2\" type=\"video/mpeg2\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"3\" />\n" +" </MediaCodec>\n" +" </Decoders>\n" +"</MediaCodecs>\n"; + +static const char kTestOverridesStrNew2[] = +"\n" +"<MediaCodecs>\n" +" <Encoders>\n" +" <MediaCodec name=\"OMX.qcom.video.encoder.mpeg4\" type=\"video/mp4v-es\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"4\" />\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.encoder.avc\" type=\"video/avc\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"4\" />\n" +" </MediaCodec>\n" +" </Encoders>\n" +" <Decoders>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.mpeg4\" type=\"video/mp4v-es\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"3\" />\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.mpeg2\" type=\"video/mpeg2\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"3\" />\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.h263\" type=\"video/3gpp\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"4\" />\n" +" </MediaCodec>\n" +" <MediaCodec name=\"OMX.qcom.video.decoder.avc.secure\" type=\"video/avc\" update=\"true\" >\n" +" <Limit name=\"max-supported-instances\" value=\"1\" />\n" +" </MediaCodec>\n" +" </Decoders>\n" +"</MediaCodecs>\n"; + +class MediaCodecListOverridesTest : public ::testing::Test { +public: + MediaCodecListOverridesTest() {} + + void verifyOverrides(const KeyedVector<AString, CodecSettings> &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<AMessage> &details, const char *name, const char *value) { + AString value1; + EXPECT_TRUE(details->findString(name, &value1)); + EXPECT_TRUE(value1 == value); + } + + void createTestInfos(Vector<sp<MediaCodecInfo>> *infos) { + const char *name = "OMX.qcom.video.decoder.avc"; + const bool encoder = false; + const char *mime = "video/avc"; + sp<MediaCodecInfo> info = new MediaCodecInfo(name, encoder, mime); + infos->push_back(info); + const sp<MediaCodecInfo::Capabilities> caps = info->getCapabilitiesFor(mime); + const sp<AMessage> 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<AString, CodecSettings> *results) { + CodecSettings settings; + settings.add("max-supported-instances", value); + results->add(key, settings); + } + + void exportTestResultsToXML(const char *fileName) { + KeyedVector<AString, CodecSettings> 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<IMediaCodecList> list = MediaCodecList::getInstance(); + Vector<sp<MediaCodecInfo>> infos; + for (size_t i = 0; i < list->countCodecs(); ++i) { + infos.push_back(list->getCodecInfo(i)); + } + KeyedVector<AString, CodecSettings> 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<sp<MediaCodecInfo>> 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<AMessage> 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 |