/* * Copyright 2013, 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 "VideoFormats" #include #include "VideoFormats.h" #include namespace android { // static const VideoFormats::config_t VideoFormats::mResolutionTable[][32] = { { // CEA Resolutions { 640, 480, 60, false, 0, 0}, { 720, 480, 60, false, 0, 0}, { 720, 480, 60, true, 0, 0}, { 720, 576, 50, false, 0, 0}, { 720, 576, 50, true, 0, 0}, { 1280, 720, 30, false, 0, 0}, { 1280, 720, 60, false, 0, 0}, { 1920, 1080, 30, false, 0, 0}, { 1920, 1080, 60, false, 0, 0}, { 1920, 1080, 60, true, 0, 0}, { 1280, 720, 25, false, 0, 0}, { 1280, 720, 50, false, 0, 0}, { 1920, 1080, 25, false, 0, 0}, { 1920, 1080, 50, false, 0, 0}, { 1920, 1080, 50, true, 0, 0}, { 1280, 720, 24, false, 0, 0}, { 1920, 1080, 24, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, }, { // VESA Resolutions { 800, 600, 30, false, 0, 0}, { 800, 600, 60, false, 0, 0}, { 1024, 768, 30, false, 0, 0}, { 1024, 768, 60, false, 0, 0}, { 1152, 864, 30, false, 0, 0}, { 1152, 864, 60, false, 0, 0}, { 1280, 768, 30, false, 0, 0}, { 1280, 768, 60, false, 0, 0}, { 1280, 800, 30, false, 0, 0}, { 1280, 800, 60, false, 0, 0}, { 1360, 768, 30, false, 0, 0}, { 1360, 768, 60, false, 0, 0}, { 1366, 768, 30, false, 0, 0}, { 1366, 768, 60, false, 0, 0}, { 1280, 1024, 30, false, 0, 0}, { 1280, 1024, 60, false, 0, 0}, { 1400, 1050, 30, false, 0, 0}, { 1400, 1050, 60, false, 0, 0}, { 1440, 900, 30, false, 0, 0}, { 1440, 900, 60, false, 0, 0}, { 1600, 900, 30, false, 0, 0}, { 1600, 900, 60, false, 0, 0}, { 1600, 1200, 30, false, 0, 0}, { 1600, 1200, 60, false, 0, 0}, { 1680, 1024, 30, false, 0, 0}, { 1680, 1024, 60, false, 0, 0}, { 1680, 1050, 30, false, 0, 0}, { 1680, 1050, 60, false, 0, 0}, { 1920, 1200, 30, false, 0, 0}, { 1920, 1200, 60, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, }, { // HH Resolutions { 800, 480, 30, false, 0, 0}, { 800, 480, 60, false, 0, 0}, { 854, 480, 30, false, 0, 0}, { 854, 480, 60, false, 0, 0}, { 864, 480, 30, false, 0, 0}, { 864, 480, 60, false, 0, 0}, { 640, 360, 30, false, 0, 0}, { 640, 360, 60, false, 0, 0}, { 960, 540, 30, false, 0, 0}, { 960, 540, 60, false, 0, 0}, { 848, 480, 30, false, 0, 0}, { 848, 480, 60, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, { 0, 0, 0, false, 0, 0}, } }; VideoFormats::VideoFormats() { memcpy(mConfigs, mResolutionTable, sizeof(mConfigs)); for (size_t i = 0; i < kNumResolutionTypes; ++i) { mResolutionEnabled[i] = 0; } setNativeResolution(RESOLUTION_CEA, 0); // default to 640x480 p60 } void VideoFormats::setNativeResolution(ResolutionType type, size_t index) { CHECK_LT(type, kNumResolutionTypes); CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL)); mNativeType = type; mNativeIndex = index; setResolutionEnabled(type, index); } void VideoFormats::getNativeResolution( ResolutionType *type, size_t *index) const { *type = mNativeType; *index = mNativeIndex; } void VideoFormats::disableAll() { for (size_t i = 0; i < kNumResolutionTypes; ++i) { mResolutionEnabled[i] = 0; for (size_t j = 0; j < 32; j++) { mConfigs[i][j].profile = mConfigs[i][j].level = 0; } } } void VideoFormats::enableAll() { for (size_t i = 0; i < kNumResolutionTypes; ++i) { mResolutionEnabled[i] = 0xffffffff; for (size_t j = 0; j < 32; j++) { mConfigs[i][j].profile = (1ul << PROFILE_CBP); mConfigs[i][j].level = (1ul << LEVEL_31); } } } void VideoFormats::enableResolutionUpto( ResolutionType type, size_t index, ProfileType profile, LevelType level) { size_t width, height, fps, score; bool interlaced; if (!GetConfiguration(type, index, &width, &height, &fps, &interlaced)) { ALOGE("Maximum resolution not found!"); return; } score = width * height * fps * (!interlaced + 1); for (size_t i = 0; i < kNumResolutionTypes; ++i) { for (size_t j = 0; j < 32; j++) { if (GetConfiguration((ResolutionType)i, j, &width, &height, &fps, &interlaced) && score >= width * height * fps * (!interlaced + 1)) { setResolutionEnabled((ResolutionType)i, j); setProfileLevel((ResolutionType)i, j, profile, level); } } } } void VideoFormats::setResolutionEnabled( ResolutionType type, size_t index, bool enabled) { CHECK_LT(type, kNumResolutionTypes); CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL)); if (enabled) { mResolutionEnabled[type] |= (1ul << index); mConfigs[type][index].profile = (1ul << PROFILE_CBP); mConfigs[type][index].level = (1ul << LEVEL_31); } else { mResolutionEnabled[type] &= ~(1ul << index); mConfigs[type][index].profile = 0; mConfigs[type][index].level = 0; } } void VideoFormats::setProfileLevel( ResolutionType type, size_t index, ProfileType profile, LevelType level) { CHECK_LT(type, kNumResolutionTypes); CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL)); mConfigs[type][index].profile = (1ul << profile); mConfigs[type][index].level = (1ul << level); } void VideoFormats::getProfileLevel( ResolutionType type, size_t index, ProfileType *profile, LevelType *level) const{ CHECK_LT(type, kNumResolutionTypes); CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL)); int i, bestProfile = -1, bestLevel = -1; for (i = 0; i < kNumProfileTypes; ++i) { if (mConfigs[type][index].profile & (1ul << i)) { bestProfile = i; } } for (i = 0; i < kNumLevelTypes; ++i) { if (mConfigs[type][index].level & (1ul << i)) { bestLevel = i; } } if (bestProfile == -1 || bestLevel == -1) { ALOGE("Profile or level not set for resolution type %d, index %zu", type, index); bestProfile = PROFILE_CBP; bestLevel = LEVEL_31; } *profile = (ProfileType) bestProfile; *level = (LevelType) bestLevel; } bool VideoFormats::isResolutionEnabled( ResolutionType type, size_t index) const { CHECK_LT(type, kNumResolutionTypes); CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL)); return mResolutionEnabled[type] & (1ul << index); } // static bool VideoFormats::GetConfiguration( ResolutionType type, size_t index, size_t *width, size_t *height, size_t *framesPerSecond, bool *interlaced) { CHECK_LT(type, kNumResolutionTypes); if (index >= 32) { return false; } const config_t *config = &mResolutionTable[type][index]; if (config->width == 0) { return false; } if (width) { *width = config->width; } if (height) { *height = config->height; } if (framesPerSecond) { *framesPerSecond = config->framesPerSecond; } if (interlaced) { *interlaced = config->interlaced; } return true; } bool VideoFormats::parseH264Codec(const char *spec) { unsigned profile, level, res[3]; if (sscanf( spec, "%02x %02x %08X %08X %08X", &profile, &level, &res[0], &res[1], &res[2]) != 5) { return false; } for (size_t i = 0; i < kNumResolutionTypes; ++i) { for (size_t j = 0; j < 32; ++j) { if (res[i] & (1ul << j)){ mResolutionEnabled[i] |= (1ul << j); if (profile > mConfigs[i][j].profile) { // prefer higher profile (even if level is lower) mConfigs[i][j].profile = profile; mConfigs[i][j].level = level; } else if (profile == mConfigs[i][j].profile && level > mConfigs[i][j].level) { mConfigs[i][j].level = level; } } } } return true; } // static bool VideoFormats::GetProfileLevel( ProfileType profile, LevelType level, unsigned *profileIdc, unsigned *levelIdc, unsigned *constraintSet) { CHECK_LT(profile, kNumProfileTypes); CHECK_LT(level, kNumLevelTypes); static const unsigned kProfileIDC[kNumProfileTypes] = { 66, // PROFILE_CBP 100, // PROFILE_CHP }; static const unsigned kLevelIDC[kNumLevelTypes] = { 31, // LEVEL_31 32, // LEVEL_32 40, // LEVEL_40 41, // LEVEL_41 42, // LEVEL_42 }; static const unsigned kConstraintSet[kNumProfileTypes] = { 0xc0, // PROFILE_CBP 0x0c, // PROFILE_CHP }; if (profileIdc) { *profileIdc = kProfileIDC[profile]; } if (levelIdc) { *levelIdc = kLevelIDC[level]; } if (constraintSet) { *constraintSet = kConstraintSet[profile]; } return true; } bool VideoFormats::parseFormatSpec(const char *spec) { CHECK_EQ(kNumResolutionTypes, 3); disableAll(); unsigned native, dummy; size_t size = strlen(spec); size_t offset = 0; if (sscanf(spec, "%02x %02x ", &native, &dummy) != 2) { return false; } offset += 6; // skip native and preferred-display-mode-supported CHECK_LE(offset + 58, size); while (offset < size) { parseH264Codec(spec + offset); offset += 60; // skip H.264-codec + ", " } mNativeIndex = native >> 3; mNativeType = (ResolutionType)(native & 7); bool success; if (mNativeType >= kNumResolutionTypes) { success = false; } else { success = GetConfiguration( mNativeType, mNativeIndex, NULL, NULL, NULL, NULL); } if (!success) { ALOGW("sink advertised an illegal native resolution, fortunately " "this value is ignored for the time being..."); } return true; } AString VideoFormats::getFormatSpec(bool forM4Message) const { CHECK_EQ(kNumResolutionTypes, 3); // wfd_video_formats: // 1 byte "native" // 1 byte "preferred-display-mode-supported" 0 or 1 // one or more avc codec structures // 1 byte profile // 1 byte level // 4 byte CEA mask // 4 byte VESA mask // 4 byte HH mask // 1 byte latency // 2 byte min-slice-slice // 2 byte slice-enc-params // 1 byte framerate-control-support // max-hres (none or 2 byte) // max-vres (none or 2 byte) return AStringPrintf( "%02x 00 %02x %02x %08x %08x %08x 00 0000 0000 00 none none", forM4Message ? 0x00 : ((mNativeIndex << 3) | mNativeType), mConfigs[mNativeType][mNativeIndex].profile, mConfigs[mNativeType][mNativeIndex].level, mResolutionEnabled[0], mResolutionEnabled[1], mResolutionEnabled[2]); } // static bool VideoFormats::PickBestFormat( const VideoFormats &sinkSupported, const VideoFormats &sourceSupported, ResolutionType *chosenType, size_t *chosenIndex, ProfileType *chosenProfile, LevelType *chosenLevel) { #if 0 // Support for the native format is a great idea, the spec includes // these features, but nobody supports it and the tests don't validate it. ResolutionType nativeType; size_t nativeIndex; sinkSupported.getNativeResolution(&nativeType, &nativeIndex); if (sinkSupported.isResolutionEnabled(nativeType, nativeIndex)) { if (sourceSupported.isResolutionEnabled(nativeType, nativeIndex)) { ALOGI("Choosing sink's native resolution"); *chosenType = nativeType; *chosenIndex = nativeIndex; return true; } } else { ALOGW("Sink advertised native resolution that it doesn't " "actually support... ignoring"); } sourceSupported.getNativeResolution(&nativeType, &nativeIndex); if (sourceSupported.isResolutionEnabled(nativeType, nativeIndex)) { if (sinkSupported.isResolutionEnabled(nativeType, nativeIndex)) { ALOGI("Choosing source's native resolution"); *chosenType = nativeType; *chosenIndex = nativeIndex; return true; } } else { ALOGW("Source advertised native resolution that it doesn't " "actually support... ignoring"); } #endif bool first = true; uint32_t bestScore = 0; size_t bestType = 0; size_t bestIndex = 0; for (size_t i = 0; i < kNumResolutionTypes; ++i) { for (size_t j = 0; j < 32; ++j) { size_t width, height, framesPerSecond; bool interlaced; if (!GetConfiguration( (ResolutionType)i, j, &width, &height, &framesPerSecond, &interlaced)) { break; } if (!sinkSupported.isResolutionEnabled((ResolutionType)i, j) || !sourceSupported.isResolutionEnabled( (ResolutionType)i, j)) { continue; } ALOGV("type %zu, index %zu, %zu x %zu %c%zu supported", i, j, width, height, interlaced ? 'i' : 'p', framesPerSecond); uint32_t score = width * height * framesPerSecond; if (!interlaced) { score *= 2; } if (first || score > bestScore) { bestScore = score; bestType = i; bestIndex = j; first = false; } } } if (first) { return false; } *chosenType = (ResolutionType)bestType; *chosenIndex = bestIndex; // Pick the best profile/level supported by both sink and source. ProfileType srcProfile, sinkProfile; LevelType srcLevel, sinkLevel; sourceSupported.getProfileLevel( (ResolutionType)bestType, bestIndex, &srcProfile, &srcLevel); sinkSupported.getProfileLevel( (ResolutionType)bestType, bestIndex, &sinkProfile, &sinkLevel); *chosenProfile = srcProfile < sinkProfile ? srcProfile : sinkProfile; *chosenLevel = srcLevel < sinkLevel ? srcLevel : sinkLevel; return true; } } // namespace android