| /* |
| * 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 <cutils/properties.h> |
| #include <gui/Surface.h> |
| #include <mediadrm/ICrypto.h> |
| #include <media/IMediaCodecList.h> |
| #include <media/MediaCodecInfo.h> |
| #include <media/MediaResourcePolicy.h> |
| #include <media/openmax/OMX_IVCommon.h> |
| #include <media/stagefright/foundation/AMessage.h> |
| #include <media/stagefright/MediaCodec.h> |
| #include <media/stagefright/MediaCodecList.h> |
| #include <media/stagefright/MediaCodecListOverrides.h> |
| |
| namespace android { |
| |
| AString getProfilingVersionString() { |
| char val[PROPERTY_VALUE_MAX]; |
| if (property_get("ro.build.display.id", val, NULL) && (strlen(val) > 0)) { |
| return AStringPrintf("<!-- Profiled-with: %s -->", val); |
| } |
| |
| return "<!-- Profiled-with: UNKNOWN_BUILD_ID -->"; |
| } |
| |
| // 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(const 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(const 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, const AString &mime, const 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); |
| format->setInt32("encoder", 1); |
| } |
| |
| 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, const AString &name, const AString &mime, const sp<MediaCodecInfo::Capabilities> &caps) { |
| sp<AMessage> format = getMeasureFormat(isEncoder, mime, caps); |
| if (format == NULL) { |
| return 0; |
| } |
| 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 #%zu", 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 = isEncoder ? MediaCodec::CONFIGURE_FLAG_ENCODE : 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(); |
| } |
| |
| 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 std::vector<sp<MediaCodecInfo>> &infos, |
| const char* profilingResults) { |
| CodecSettings global_results; |
| KeyedVector<AString, CodecSettings> encoder_results; |
| KeyedVector<AString, CodecSettings> decoder_results; |
| profileCodecs(infos, &global_results, &encoder_results, &decoder_results); |
| exportResultsToXML(profilingResults, global_results, encoder_results, decoder_results); |
| } |
| |
| void profileCodecs( |
| const std::vector<sp<MediaCodecInfo>> &infos, |
| CodecSettings *global_results, |
| KeyedVector<AString, CodecSettings> *encoder_results, |
| KeyedVector<AString, CodecSettings> *decoder_results, |
| bool forceToMeasure) { |
| KeyedVector<AString, sp<MediaCodecInfo::Capabilities>> codecsNeedMeasure; |
| AString supportMultipleSecureCodecs = "true"; |
| for (const auto& info : infos) { |
| AString name = info->getCodecName(); |
| if (name.startsWith("OMX.google.") || name.startsWith("c2.android.") || |
| // TODO: reenable below codecs once fixed |
| name == "OMX.Intel.VideoDecoder.VP9.hybrid") { |
| continue; |
| } |
| |
| Vector<AString> mediaTypes; |
| info->getSupportedMediaTypes(&mediaTypes); |
| for (size_t i = 0; i < mediaTypes.size(); ++i) { |
| const sp<MediaCodecInfo::Capabilities> &caps = |
| info->getCapabilitiesFor(mediaTypes[i].c_str()); |
| if (!forceToMeasure && |
| (caps->getDetails()->contains("max-supported-instances") || |
| caps->getDetails()->contains("max-concurrent-instances"))) { |
| continue; |
| } |
| |
| size_t max = doProfileCodecs(info->isEncoder(), name, mediaTypes[i], caps); |
| if (max > 0) { |
| CodecSettings settings; |
| char maxStr[32]; |
| sprintf(maxStr, "%zu", max); |
| settings.add("max-supported-instances", maxStr); |
| |
| AString key = name; |
| key.append(" "); |
| key.append(mediaTypes[i]); |
| |
| if (info->isEncoder()) { |
| encoder_results->add(key, settings); |
| } else { |
| decoder_results->add(key, settings); |
| } |
| |
| if (name.endsWith(".secure")) { |
| if (max <= 1) { |
| supportMultipleSecureCodecs = "false"; |
| } |
| } |
| } |
| } |
| } |
| global_results->add( |
| MediaResourcePolicy::kPolicySupportsMultipleSecureCodecs(), |
| supportMultipleSecureCodecs); |
| } |
| |
| static AString globalResultsToXml(const CodecSettings &results) { |
| AString ret; |
| for (size_t i = 0; i < results.size(); ++i) { |
| AString setting = AStringPrintf( |
| " <Setting name=\"%s\" value=\"%s\" />\n", |
| results.keyAt(i).c_str(), |
| results.valueAt(i).c_str()); |
| ret.append(setting); |
| } |
| return ret; |
| } |
| |
| static AString codecResultsToXml(const KeyedVector<AString, CodecSettings> &results) { |
| AString ret; |
| for (size_t i = 0; i < results.size(); ++i) { |
| AString name; |
| AString mime; |
| if (!splitString(results.keyAt(i), " ", &name, &mime)) { |
| continue; |
| } |
| AString codec = |
| AStringPrintf(" <MediaCodec name=\"%s\" type=\"%s\" update=\"true\" >\n", |
| name.c_str(), |
| mime.c_str()); |
| ret.append(codec); |
| const 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 setting = AStringPrintf( |
| " <Limit name=\"%s\" value=\"%s\" />\n", |
| settings.keyAt(i).c_str(), |
| settings.valueAt(i).c_str()); |
| ret.append(setting); |
| } |
| ret.append(" </MediaCodec>\n"); |
| } |
| return ret; |
| } |
| |
| void exportResultsToXML( |
| const char *fileName, |
| const CodecSettings &global_results, |
| const KeyedVector<AString, CodecSettings> &encoder_results, |
| const KeyedVector<AString, CodecSettings> &decoder_results) { |
| if (global_results.size() == 0 && encoder_results.size() == 0 && decoder_results.size() == 0) { |
| return; |
| } |
| |
| AString overrides; |
| overrides.append(getProfilingVersionString()); |
| overrides.append("\n"); |
| overrides.append("<MediaCodecs>\n"); |
| if (global_results.size() > 0) { |
| overrides.append(" <Settings>\n"); |
| overrides.append(globalResultsToXml(global_results)); |
| overrides.append(" </Settings>\n"); |
| } |
| if (encoder_results.size() > 0) { |
| overrides.append(" <Encoders>\n"); |
| overrides.append(codecResultsToXml(encoder_results)); |
| overrides.append(" </Encoders>\n"); |
| } |
| if (decoder_results.size() > 0) { |
| overrides.append(" <Decoders>\n"); |
| overrides.append(codecResultsToXml(decoder_results)); |
| overrides.append(" </Decoders>\n"); |
| } |
| overrides.append("</MediaCodecs>\n"); |
| |
| FILE *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 |