1 /*
2  * Copyright (C) 2019 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 #define LOG_TAG "HeicEncoderInfoManager"
18 //#define LOG_NDEBUG 0
19 
20 #include <cstdint>
21 #include <regex>
22 
23 #include <cutils/properties.h>
24 #include <log/log_main.h>
25 #include <system/graphics.h>
26 
27 #include <media/stagefright/MediaCodecList.h>
28 #include <media/stagefright/foundation/MediaDefs.h>
29 #include <media/stagefright/foundation/ABuffer.h>
30 
31 #include "HeicEncoderInfoManager.h"
32 
33 namespace android {
34 namespace camera3 {
35 
HeicEncoderInfoManager()36 HeicEncoderInfoManager::HeicEncoderInfoManager() :
37         mIsInited(false),
38         mMinSizeHeic(0, 0),
39         mMaxSizeHeic(INT32_MAX, INT32_MAX),
40         mHasHEVC(false),
41         mHasHEIC(false),
42         mDisableGrid(false) {
43     if (initialize() == OK) {
44         mIsInited = true;
45     }
46 }
47 
~HeicEncoderInfoManager()48 HeicEncoderInfoManager::~HeicEncoderInfoManager() {
49 }
50 
isSizeSupported(int32_t width,int32_t height,bool * useHeic,bool * useGrid,int64_t * stall,AString * hevcName) const51 bool HeicEncoderInfoManager::isSizeSupported(int32_t width, int32_t height, bool* useHeic,
52         bool* useGrid, int64_t* stall, AString* hevcName) const {
53     if (useHeic == nullptr || useGrid == nullptr) {
54         ALOGE("%s: invalid parameters: useHeic %p, useGrid %p",
55                 __FUNCTION__, useHeic, useGrid);
56         return false;
57     }
58     if (!mIsInited) return false;
59 
60     bool chooseHeic = false, enableGrid = true;
61     if (mHasHEIC && width >= mMinSizeHeic.first &&
62             height >= mMinSizeHeic.second && width <= mMaxSizeHeic.first &&
63             height <= mMaxSizeHeic.second) {
64         chooseHeic = true;
65         enableGrid = false;
66     } else if (mHasHEVC) {
67         bool fullSizeSupportedByHevc = (width >= mMinSizeHevc.first &&
68                 height >= mMinSizeHevc.second &&
69                 width <= mMaxSizeHevc.first &&
70                 height <= mMaxSizeHevc.second);
71         if (fullSizeSupportedByHevc && (mDisableGrid ||
72                 (width <= 1920 && height <= 1080))) {
73             enableGrid = false;
74         }
75         if (hevcName != nullptr) {
76             *hevcName = mHevcName;
77         }
78     } else {
79         // No encoder available for the requested size.
80         return false;
81     }
82 
83     if (stall != nullptr) {
84         // Find preferred encoder which advertise
85         // "measured-frame-rate-WIDTHxHEIGHT-range" key.
86         const FrameRateMaps& maps =
87                 (chooseHeic && mHeicFrameRateMaps.size() > 0) ?
88                 mHeicFrameRateMaps : mHevcFrameRateMaps;
89         const auto& closestSize = findClosestSize(maps, width, height);
90         if (closestSize == maps.end()) {
91             // The "measured-frame-rate-WIDTHxHEIGHT-range" key is optional.
92             // Hardcode to some default value (3.33ms * tile count) based on resolution.
93             *stall = 3333333LL * width * height / (kGridWidth * kGridHeight);
94             *useHeic = chooseHeic;
95             *useGrid = enableGrid;
96             return true;
97         }
98 
99         // Derive stall durations based on average fps of the closest size.
100         constexpr int64_t NSEC_PER_SEC = 1000000000LL;
101         int32_t avgFps = (closestSize->second.first + closestSize->second.second)/2;
102         float ratio = 1.0f * width * height /
103                 (closestSize->first.first * closestSize->first.second);
104         *stall = ratio * NSEC_PER_SEC / avgFps;
105     }
106 
107     *useHeic = chooseHeic;
108     *useGrid = enableGrid;
109     return true;
110 }
111 
initialize()112 status_t HeicEncoderInfoManager::initialize() {
113     mDisableGrid = property_get_bool("camera.heic.disable_grid", false);
114     sp<IMediaCodecList> codecsList = MediaCodecList::getInstance();
115     if (codecsList == nullptr) {
116         // No media codec available.
117         return OK;
118     }
119 
120     sp<AMessage> heicDetails = getCodecDetails(codecsList, MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC);
121 
122     if (!getHevcCodecDetails(codecsList, MEDIA_MIMETYPE_VIDEO_HEVC)) {
123         if (heicDetails != nullptr) {
124             ALOGE("%s: Device must support HEVC codec if HEIC codec is available!",
125                     __FUNCTION__);
126             return BAD_VALUE;
127         }
128         return OK;
129     }
130     mHasHEVC = true;
131 
132     // HEIC size range
133     if (heicDetails != nullptr) {
134         auto res = getCodecSizeRange(MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC,
135                 heicDetails, &mMinSizeHeic, &mMaxSizeHeic, &mHeicFrameRateMaps);
136         if (res != OK) {
137             ALOGE("%s: Failed to get HEIC codec size range: %s (%d)", __FUNCTION__,
138                     strerror(-res), res);
139             return BAD_VALUE;
140         }
141         mHasHEIC = true;
142     }
143 
144     return OK;
145 }
146 
getFrameRateMaps(sp<AMessage> details,FrameRateMaps * maps)147 status_t HeicEncoderInfoManager::getFrameRateMaps(sp<AMessage> details, FrameRateMaps* maps) {
148     if (details == nullptr || maps == nullptr) {
149         ALOGE("%s: Invalid input: details: %p, maps: %p", __FUNCTION__, details.get(), maps);
150         return BAD_VALUE;
151     }
152 
153     for (size_t i = 0; i < details->countEntries(); i++) {
154         AMessage::Type type;
155         const char* entryName = details->getEntryNameAt(i, &type);
156         if (type != AMessage::kTypeString) continue;
157         std::regex frameRateNamePattern("measured-frame-rate-([0-9]+)[*x]([0-9]+)-range",
158                 std::regex_constants::icase);
159         std::cmatch sizeMatch;
160         if (std::regex_match(entryName, sizeMatch, frameRateNamePattern) &&
161                 sizeMatch.size() == 3) {
162             AMessage::ItemData item = details->getEntryAt(i);
163             AString fpsRangeStr;
164             if (item.find(&fpsRangeStr)) {
165                 ALOGV("%s: %s", entryName, fpsRangeStr.c_str());
166                 std::regex frameRatePattern("([0-9]+)-([0-9]+)");
167                 std::cmatch fpsMatch;
168                 if (std::regex_match(fpsRangeStr.c_str(), fpsMatch, frameRatePattern) &&
169                         fpsMatch.size() == 3) {
170                     maps->emplace(
171                             std::make_pair(stoi(sizeMatch[1]), stoi(sizeMatch[2])),
172                             std::make_pair(stoi(fpsMatch[1]), stoi(fpsMatch[2])));
173                 } else {
174                     return BAD_VALUE;
175                 }
176             }
177         }
178     }
179     return OK;
180 }
181 
getCodecSizeRange(const char * codecName,sp<AMessage> details,std::pair<int32_t,int32_t> * minSize,std::pair<int32_t,int32_t> * maxSize,FrameRateMaps * frameRateMaps)182 status_t HeicEncoderInfoManager::getCodecSizeRange(
183         const char* codecName,
184         sp<AMessage> details,
185         std::pair<int32_t, int32_t>* minSize,
186         std::pair<int32_t, int32_t>* maxSize,
187         FrameRateMaps* frameRateMaps) {
188     if (codecName == nullptr || minSize == nullptr || maxSize == nullptr ||
189             details == nullptr || frameRateMaps == nullptr) {
190         return BAD_VALUE;
191     }
192 
193     AString sizeRange;
194     auto hasItem = details->findString("size-range", &sizeRange);
195     if (!hasItem) {
196         ALOGE("%s: Failed to query size range for codec %s", __FUNCTION__, codecName);
197         return BAD_VALUE;
198     }
199     ALOGV("%s: %s codec's size range is %s", __FUNCTION__, codecName, sizeRange.c_str());
200     std::regex pattern("([0-9]+)[*x]([0-9]+)-([0-9]+)[*x]([0-9]+)");
201     std::cmatch match;
202     if (std::regex_match(sizeRange.c_str(), match, pattern)) {
203         if (match.size() == 5) {
204             minSize->first = stoi(match[1]);
205             minSize->second = stoi(match[2]);
206             maxSize->first = stoi(match[3]);
207             maxSize->second = stoi(match[4]);
208             if (minSize->first > maxSize->first ||
209                     minSize->second > maxSize->second) {
210                 ALOGE("%s: Invalid %s code size range: %s",
211                         __FUNCTION__, codecName, sizeRange.c_str());
212                 return BAD_VALUE;
213             }
214         } else {
215             return BAD_VALUE;
216         }
217     }
218 
219     auto res = getFrameRateMaps(details, frameRateMaps);
220     if (res != OK) {
221         return res;
222     }
223 
224     return OK;
225 }
226 
findClosestSize(const FrameRateMaps & maps,int32_t width,int32_t height) const227 HeicEncoderInfoManager::FrameRateMaps::const_iterator HeicEncoderInfoManager::findClosestSize(
228         const FrameRateMaps& maps, int32_t width, int32_t height) const {
229     int32_t minDiff = INT32_MAX;
230     FrameRateMaps::const_iterator closestIter = maps.begin();
231     for (auto iter = maps.begin(); iter != maps.end(); iter++) {
232         // Use area difference between the sizes to approximate size
233         // difference.
234         int32_t diff = abs(iter->first.first * iter->first.second - width * height);
235         if (diff < minDiff) {
236             closestIter = iter;
237             minDiff = diff;
238         }
239     }
240     return closestIter;
241 }
242 
getCodecDetails(sp<IMediaCodecList> codecsList,const char * name)243 sp<AMessage> HeicEncoderInfoManager::getCodecDetails(
244         sp<IMediaCodecList> codecsList, const char* name) {
245     ssize_t idx = codecsList->findCodecByType(name, true /*encoder*/);
246     if (idx < 0) {
247         return nullptr;
248     }
249 
250     const sp<MediaCodecInfo> info = codecsList->getCodecInfo(idx);
251     if (info == nullptr) {
252         ALOGE("%s: Failed to get codec info for %s", __FUNCTION__, name);
253         return nullptr;
254     }
255     const sp<MediaCodecInfo::Capabilities> caps =
256             info->getCapabilitiesFor(name);
257     if (caps == nullptr) {
258         ALOGE("%s: Failed to get capabilities for codec %s", __FUNCTION__, name);
259         return nullptr;
260     }
261     const sp<AMessage> details = caps->getDetails();
262     if (details == nullptr) {
263         ALOGE("%s: Failed to get details for codec %s", __FUNCTION__, name);
264         return nullptr;
265     }
266 
267     return details;
268 }
269 
getHevcCodecDetails(sp<IMediaCodecList> codecsList,const char * mime)270 bool HeicEncoderInfoManager::getHevcCodecDetails(
271         sp<IMediaCodecList> codecsList, const char* mime) {
272     bool found = false;
273     ssize_t idx = 0;
274     while ((idx = codecsList->findCodecByType(mime, true /*encoder*/, idx)) >= 0) {
275         const sp<MediaCodecInfo> info = codecsList->getCodecInfo(idx++);
276         if (info == nullptr) {
277             ALOGE("%s: Failed to get codec info for %s", __FUNCTION__, mime);
278             break;
279         }
280         ALOGV("%s: [%s] codec found", __FUNCTION__,
281                 info->getCodecName());
282 
283         // Filter out software ones as they may be too slow
284         if (!(info->getAttributes() & MediaCodecInfo::kFlagIsHardwareAccelerated)) {
285             ALOGV("%s: [%s] Filter out software ones as they may be too slow", __FUNCTION__,
286                     info->getCodecName());
287             continue;
288         }
289 
290         const sp<MediaCodecInfo::Capabilities> caps =
291                 info->getCapabilitiesFor(mime);
292         if (caps == nullptr) {
293             ALOGE("%s: [%s] Failed to get capabilities", __FUNCTION__,
294                     info->getCodecName());
295             break;
296         }
297         const sp<AMessage> details = caps->getDetails();
298         if (details == nullptr) {
299             ALOGE("%s: [%s] Failed to get details", __FUNCTION__,
300                     info->getCodecName());
301             break;
302         }
303 
304         // Check CQ mode
305         AString bitrateModes;
306         auto hasItem = details->findString("feature-bitrate-modes", &bitrateModes);
307         if (!hasItem) {
308             ALOGE("%s: [%s] Failed to query bitrate modes", __FUNCTION__,
309                     info->getCodecName());
310             break;
311         }
312         ALOGV("%s: [%s] feature-bitrate-modes value is %d, %s",
313                 __FUNCTION__, info->getCodecName(), hasItem, bitrateModes.c_str());
314         std::regex pattern("(^|,)CQ($|,)", std::regex_constants::icase);
315         if (!std::regex_search(bitrateModes.c_str(), pattern)) {
316             continue; // move on to next encoder
317         }
318 
319         std::pair<int32_t, int32_t> minSizeHevc, maxSizeHevc;
320         FrameRateMaps hevcFrameRateMaps;
321         auto res = getCodecSizeRange(MEDIA_MIMETYPE_VIDEO_HEVC,
322                 details, &minSizeHevc, &maxSizeHevc, &hevcFrameRateMaps);
323         if (res != OK) {
324             ALOGE("%s: [%s] Failed to get size range: %s (%d)", __FUNCTION__,
325                     info->getCodecName(), strerror(-res), res);
326             break;
327         }
328         if (kGridWidth < minSizeHevc.first
329                 || kGridWidth > maxSizeHevc.first
330                 || kGridHeight < minSizeHevc.second
331                 || kGridHeight > maxSizeHevc.second) {
332             continue; // move on to next encoder
333         }
334 
335         // Found: save name, size, frame rate
336         mHevcName = info->getCodecName();
337         mMinSizeHevc = minSizeHevc;
338         mMaxSizeHevc = maxSizeHevc;
339         mHevcFrameRateMaps = hevcFrameRateMaps;
340 
341         found = true;
342         break;
343     }
344 
345     return found;
346 }
347 
348 } //namespace camera3
349 } // namespace android
350