1 /* 2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.camera.video; 18 19 import static androidx.camera.core.AspectRatio.RATIO_16_9; 20 import static androidx.camera.core.AspectRatio.RATIO_4_3; 21 import static androidx.camera.core.AspectRatio.RATIO_DEFAULT; 22 23 import static java.lang.Math.abs; 24 import static java.util.Objects.requireNonNull; 25 26 import android.util.Range; 27 import android.util.Rational; 28 import android.util.Size; 29 30 import androidx.camera.core.AspectRatio; 31 import androidx.camera.core.impl.utils.AspectRatioUtil; 32 import androidx.camera.core.internal.utils.SizeUtil; 33 34 import com.google.auto.value.AutoValue; 35 36 import org.jspecify.annotations.NonNull; 37 import org.jspecify.annotations.Nullable; 38 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Map; 44 45 /** 46 * This class saves the mapping from a {@link Quality} + {@code VideoSpec#ASPECT_RATIO_*} 47 * combination to a resolution list. 48 * 49 * <p>The class defines the video height range for each Quality. It classifies the input 50 * resolutions by the Quality ranges and aspect ratios. For example, assume the input resolutions 51 * are [1920x1080, 1440x1080, 1080x1080, 1280x720, 960x720 864x480, 640x480, 640x360], 52 * <pre>{@code 53 * SD-4:3 = [640x480] 54 * SD-16:9 = [640x360, 864x480] 55 * HD-4:3 = [960x720] 56 * HD-16:9 = [1280x720] 57 * FHD-4:3 = [1440x1080] 58 * FHD-16:9 = [1920x1080] 59 * }</pre> 60 * It ignores resolutions not belong to the supported aspect ratios. It sorts each resolution 61 * list based on the smallest area difference to the given video size of CamcorderProfile. 62 * It provides {@link #getResolutions(Quality, int)} API to query the result. 63 */ 64 class QualityRatioToResolutionsTable { 65 66 // Key: Quality 67 // Value: the height range of Quality 68 private static final Map<Quality, Range<Integer>> sQualityRangeMap = new HashMap<>(); 69 static { sQualityRangeMap.put(Quality.UHD, Range.create(2160, 4319))70 sQualityRangeMap.put(Quality.UHD, Range.create(2160, 4319)); sQualityRangeMap.put(Quality.FHD, Range.create(1080, 1439))71 sQualityRangeMap.put(Quality.FHD, Range.create(1080, 1439)); sQualityRangeMap.put(Quality.HD, Range.create(720, 1079))72 sQualityRangeMap.put(Quality.HD, Range.create(720, 1079)); sQualityRangeMap.put(Quality.SD, Range.create(241, 719))73 sQualityRangeMap.put(Quality.SD, Range.create(241, 719)); 74 } 75 76 // Key: aspect ratio constant 77 // Value: aspect ratio rational 78 private static final Map<Integer, Rational> sAspectRatioMap = new HashMap<>(); 79 static { sAspectRatioMap.put(RATIO_4_3, AspectRatioUtil.ASPECT_RATIO_4_3)80 sAspectRatioMap.put(RATIO_4_3, AspectRatioUtil.ASPECT_RATIO_4_3); sAspectRatioMap.put(RATIO_16_9, AspectRatioUtil.ASPECT_RATIO_16_9)81 sAspectRatioMap.put(RATIO_16_9, AspectRatioUtil.ASPECT_RATIO_16_9); 82 } 83 84 // Key: QualityRatio (Quality + AspectRatio) 85 // Value: resolutions 86 private final Map<QualityRatio, List<Size>> mTable = new HashMap<>(); 87 { 88 for (Quality quality : sQualityRangeMap.keySet()) { ArrayList()89 mTable.put(QualityRatio.of(quality, RATIO_DEFAULT), new ArrayList<>()); 90 for (Integer aspectRatio : sAspectRatioMap.keySet()) { ArrayList()91 mTable.put(QualityRatio.of(quality, aspectRatio), new ArrayList<>()); 92 } 93 } 94 } 95 96 /** 97 * Constructs table. 98 * 99 * @param resolutions the resolutions to be classified. 100 * @param profileQualityToSizeMap the video sizes of CamcorderProfile. It will be used to map 101 * [quality + {@link AspectRatio#RATIO_DEFAULT}] to the profile 102 * size, and used to sort each Quality-Ratio row by the 103 * smallest area difference to the profile size. 104 */ QualityRatioToResolutionsTable(@onNull List<Size> resolutions, @NonNull Map<Quality, Size> profileQualityToSizeMap)105 QualityRatioToResolutionsTable(@NonNull List<Size> resolutions, 106 @NonNull Map<Quality, Size> profileQualityToSizeMap) { 107 addProfileSizesToTable(profileQualityToSizeMap); 108 addResolutionsToTable(resolutions); 109 sortQualityRatioRow(profileQualityToSizeMap); 110 } 111 112 /** 113 * Gets the resolutions of the mapped Quality + AspectRatio. 114 * 115 * <p>Giving {@link AspectRatio#RATIO_DEFAULT} will return the mapped profile size. 116 */ getResolutions(@onNull Quality quality, @AspectRatio.Ratio int aspectRatio)117 @NonNull List<Size> getResolutions(@NonNull Quality quality, 118 @AspectRatio.Ratio int aspectRatio) { 119 List<Size> qualityRatioRow = getQualityRatioRow(quality, aspectRatio); 120 return qualityRatioRow != null ? new ArrayList<>(qualityRatioRow) : new ArrayList<>(0); 121 } 122 addProfileSizesToTable(@onNull Map<Quality, Size> profileQualityToSizeMap)123 private void addProfileSizesToTable(@NonNull Map<Quality, Size> profileQualityToSizeMap) { 124 for (Map.Entry<Quality, Size> entry : profileQualityToSizeMap.entrySet()) { 125 requireNonNull(getQualityRatioRow(entry.getKey(), RATIO_DEFAULT)).add(entry.getValue()); 126 } 127 } 128 addResolutionsToTable(@onNull List<Size> resolutions)129 private void addResolutionsToTable(@NonNull List<Size> resolutions) { 130 for (Size resolution : resolutions) { 131 Quality quality = findMappedQuality(resolution); 132 if (quality == null) { 133 continue; 134 } 135 Integer aspectRatio = findMappedAspectRatio(resolution); 136 if (aspectRatio == null) { 137 continue; 138 } 139 List<Size> qualityRatioRow = requireNonNull(getQualityRatioRow(quality, aspectRatio)); 140 qualityRatioRow.add(resolution); 141 } 142 } 143 sortQualityRatioRow(@onNull Map<Quality, Size> profileQualityToSizeMap)144 private void sortQualityRatioRow(@NonNull Map<Quality, Size> profileQualityToSizeMap) { 145 for (Map.Entry<QualityRatio, List<Size>> entry : mTable.entrySet()) { 146 Size profileSize = profileQualityToSizeMap.get(entry.getKey().getQuality()); 147 if (profileSize == null) { 148 // Sorting is ignored if the profile doesn't contain the corresponding size. 149 continue; 150 } 151 // Sort by the smallest area difference from the profile size. 152 int qualitySizeArea = SizeUtil.getArea(profileSize); 153 Collections.sort(entry.getValue(), (s1, s2) -> { 154 int s1Diff = abs(SizeUtil.getArea(s1) - qualitySizeArea); 155 int s2Diff = abs(SizeUtil.getArea(s2) - qualitySizeArea); 156 return s1Diff - s2Diff; 157 }); 158 } 159 } 160 findMappedQuality(@onNull Size resolution)161 private static @Nullable Quality findMappedQuality(@NonNull Size resolution) { 162 for (Map.Entry<Quality, Range<Integer>> entry : sQualityRangeMap.entrySet()) { 163 if (entry.getValue().contains(resolution.getHeight())) { 164 return entry.getKey(); 165 } 166 } 167 return null; 168 } 169 findMappedAspectRatio(@onNull Size resolution)170 private static @Nullable Integer findMappedAspectRatio(@NonNull Size resolution) { 171 for (Map.Entry<Integer, Rational> entry : sAspectRatioMap.entrySet()) { 172 if (AspectRatioUtil.hasMatchingAspectRatio(resolution, entry.getValue(), 173 SizeUtil.RESOLUTION_QVGA)) { 174 return entry.getKey(); 175 } 176 } 177 return null; 178 } 179 getQualityRatioRow(@onNull Quality quality, @AspectRatio.Ratio int aspectRatio)180 private @Nullable List<Size> getQualityRatioRow(@NonNull Quality quality, 181 @AspectRatio.Ratio int aspectRatio) { 182 return mTable.get(QualityRatio.of(quality, aspectRatio)); 183 } 184 185 @AutoValue 186 abstract static class QualityRatio { 187 of(@onNull Quality quality, @AspectRatio.Ratio int aspectRatio)188 static QualityRatio of(@NonNull Quality quality, @AspectRatio.Ratio int aspectRatio) { 189 return new AutoValue_QualityRatioToResolutionsTable_QualityRatio(quality, aspectRatio); 190 } 191 getQuality()192 abstract @NonNull Quality getQuality(); 193 194 @SuppressWarnings("unused") 195 @AspectRatio.Ratio getAspectRatio()196 abstract int getAspectRatio(); 197 } 198 } 199