xref: /aosp_15_r20/external/oboe/src/common/QuirksManager.cpp (revision 05767d913155b055644481607e6fa1e35e2fe72c)
1 /*
2  * Copyright 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 #include <oboe/AudioStreamBuilder.h>
18 #include <oboe/Oboe.h>
19 
20 #include "OboeDebug.h"
21 #include "QuirksManager.h"
22 
23 using namespace oboe;
24 
clipBufferSize(AudioStream & stream,int32_t requestedSize)25 int32_t QuirksManager::DeviceQuirks::clipBufferSize(AudioStream &stream,
26             int32_t requestedSize) {
27     if (!OboeGlobals::areWorkaroundsEnabled()) {
28         return requestedSize;
29     }
30     int bottomMargin = kDefaultBottomMarginInBursts;
31     int topMargin = kDefaultTopMarginInBursts;
32     if (isMMapUsed(stream)) {
33         if (stream.getSharingMode() == SharingMode::Exclusive) {
34             bottomMargin = getExclusiveBottomMarginInBursts();
35             topMargin = getExclusiveTopMarginInBursts();
36         }
37     } else {
38         bottomMargin = kLegacyBottomMarginInBursts;
39     }
40 
41     int32_t burst = stream.getFramesPerBurst();
42     int32_t minSize = bottomMargin * burst;
43     int32_t adjustedSize = requestedSize;
44     if (adjustedSize < minSize ) {
45         adjustedSize = minSize;
46     } else {
47         int32_t maxSize = stream.getBufferCapacityInFrames() - (topMargin * burst);
48         if (adjustedSize > maxSize ) {
49             adjustedSize = maxSize;
50         }
51     }
52     return adjustedSize;
53 }
54 
isAAudioMMapPossible(const AudioStreamBuilder & builder) const55 bool QuirksManager::DeviceQuirks::isAAudioMMapPossible(const AudioStreamBuilder &builder) const {
56     bool isSampleRateCompatible =
57             builder.getSampleRate() == oboe::Unspecified
58             || builder.getSampleRate() == kCommonNativeRate
59             || builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None;
60     return builder.getPerformanceMode() == PerformanceMode::LowLatency
61             && isSampleRateCompatible
62             && builder.getChannelCount() <= kChannelCountStereo;
63 }
64 
shouldConvertFloatToI16ForOutputStreams()65 bool QuirksManager::DeviceQuirks::shouldConvertFloatToI16ForOutputStreams() {
66     std::string productManufacturer = getPropertyString("ro.product.manufacturer");
67     if (getSdkVersion() < __ANDROID_API_L__) {
68         return true;
69     } else if ((productManufacturer == "vivo") && (getSdkVersion() < __ANDROID_API_M__)) {
70         return true;
71     }
72     return false;
73 }
74 
75 /**
76  * This is for Samsung Exynos quirks. Samsung Mobile uses Qualcomm chips so
77  * the QualcommDeviceQuirks would apply.
78  */
79 class SamsungExynosDeviceQuirks : public  QuirksManager::DeviceQuirks {
80 public:
SamsungExynosDeviceQuirks()81     SamsungExynosDeviceQuirks() {
82         std::string chipname = getPropertyString("ro.hardware.chipname");
83         isExynos9810 = (chipname == "exynos9810");
84         isExynos990 = (chipname == "exynos990");
85         isExynos850 = (chipname == "exynos850");
86 
87         mBuildChangelist = getPropertyInteger("ro.build.changelist", 0);
88     }
89 
90     virtual ~SamsungExynosDeviceQuirks() = default;
91 
getExclusiveBottomMarginInBursts() const92     int32_t getExclusiveBottomMarginInBursts() const override {
93         return kBottomMargin;
94     }
95 
getExclusiveTopMarginInBursts() const96     int32_t getExclusiveTopMarginInBursts() const override {
97         return kTopMargin;
98     }
99 
100     // See Oboe issues #824 and #1247 for more information.
isMonoMMapActuallyStereo() const101     bool isMonoMMapActuallyStereo() const override {
102         return isExynos9810 || isExynos850; // TODO We can make this version specific if it gets fixed.
103     }
104 
isAAudioMMapPossible(const AudioStreamBuilder & builder) const105     bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const override {
106         return DeviceQuirks::isAAudioMMapPossible(builder)
107                 // Samsung says they use Legacy for Camcorder
108                 && builder.getInputPreset() != oboe::InputPreset::Camcorder;
109     }
110 
isMMapSafe(const AudioStreamBuilder & builder)111     bool isMMapSafe(const AudioStreamBuilder &builder) override {
112         const bool isInput = builder.getDirection() == Direction::Input;
113         // This detects b/159066712 , S20 LSI has corrupt low latency audio recording
114         // and turns off MMAP.
115         // See also https://github.com/google/oboe/issues/892
116         bool isRecordingCorrupted = isInput
117             && isExynos990
118             && mBuildChangelist < 19350896;
119 
120         // Certain S9+ builds record silence when using MMAP and not using the VoiceCommunication
121         // preset.
122         // See https://github.com/google/oboe/issues/1110
123         bool wouldRecordSilence = isInput
124             && isExynos9810
125             && mBuildChangelist <= 18847185
126             && (builder.getInputPreset() != InputPreset::VoiceCommunication);
127 
128         if (wouldRecordSilence){
129             LOGI("QuirksManager::%s() Requested stream configuration would result in silence on "
130                  "this device. Switching off MMAP.", __func__);
131         }
132 
133         return !isRecordingCorrupted && !wouldRecordSilence;
134     }
135 
136 private:
137     // Stay farther away from DSP position on Exynos devices.
138     static constexpr int32_t kBottomMargin = 2;
139     static constexpr int32_t kTopMargin = 1;
140     bool isExynos9810 = false;
141     bool isExynos990 = false;
142     bool isExynos850 = false;
143     int mBuildChangelist = 0;
144 };
145 
146 class QualcommDeviceQuirks : public  QuirksManager::DeviceQuirks {
147 public:
QualcommDeviceQuirks()148     QualcommDeviceQuirks() {
149         std::string modelName = getPropertyString("ro.soc.model");
150         isSM8150 = (modelName == "SDM8150");
151     }
152 
153     virtual ~QualcommDeviceQuirks() = default;
154 
getExclusiveBottomMarginInBursts() const155     int32_t getExclusiveBottomMarginInBursts() const override {
156         return kBottomMargin;
157     }
158 
isMMapSafe(const AudioStreamBuilder & builder)159     bool isMMapSafe(const AudioStreamBuilder &builder) override {
160         // See https://github.com/google/oboe/issues/1121#issuecomment-897957749
161         bool isMMapBroken = false;
162         if (isSM8150 && (getSdkVersion() <= __ANDROID_API_P__)) {
163             LOGI("QuirksManager::%s() MMAP not actually supported on this chip."
164                  " Switching off MMAP.", __func__);
165             isMMapBroken = true;
166         }
167 
168         return !isMMapBroken;
169     }
170 
171 private:
172     bool isSM8150 = false;
173     static constexpr int32_t kBottomMargin = 1;
174 };
175 
QuirksManager()176 QuirksManager::QuirksManager() {
177     std::string productManufacturer = getPropertyString("ro.product.manufacturer");
178     if (productManufacturer == "samsung") {
179         std::string arch = getPropertyString("ro.arch");
180         bool isExynos = (arch.rfind("exynos", 0) == 0); // starts with?
181         if (isExynos) {
182             mDeviceQuirks = std::make_unique<SamsungExynosDeviceQuirks>();
183         }
184     }
185     if (!mDeviceQuirks) {
186         std::string socManufacturer = getPropertyString("ro.soc.manufacturer");
187         if (socManufacturer == "Qualcomm") {
188             // This may include Samsung Mobile devices.
189             mDeviceQuirks = std::make_unique<QualcommDeviceQuirks>();
190         } else {
191             mDeviceQuirks = std::make_unique<DeviceQuirks>();
192         }
193     }
194 }
195 
isConversionNeeded(const AudioStreamBuilder & builder,AudioStreamBuilder & childBuilder)196 bool QuirksManager::isConversionNeeded(
197         const AudioStreamBuilder &builder,
198         AudioStreamBuilder &childBuilder) {
199     bool conversionNeeded = false;
200     const bool isLowLatency = builder.getPerformanceMode() == PerformanceMode::LowLatency;
201     const bool isInput = builder.getDirection() == Direction::Input;
202     const bool isFloat = builder.getFormat() == AudioFormat::Float;
203     const bool isIEC61937 = builder.getFormat() == AudioFormat::IEC61937;
204 
205     // There should be no conversion for IEC61937. Sample rates and channel counts must be set explicitly.
206     if (isIEC61937) {
207         LOGI("QuirksManager::%s() conversion not needed for IEC61937", __func__);
208         return false;
209     }
210 
211     // There are multiple bugs involving using callback with a specified callback size.
212     // Issue #778: O to Q had a problem with Legacy INPUT streams for FLOAT streams
213     // and a specified callback size. It would assert because of a bad buffer size.
214     //
215     // Issue #973: O to R had a problem with Legacy output streams using callback and a specified callback size.
216     // An AudioTrack stream could still be running when the AAudio FixedBlockReader was closed.
217     // Internally b/161914201#comment25
218     //
219     // Issue #983: O to R would glitch if the framesPerCallback was too small.
220     //
221     // Most of these problems were related to Legacy stream. MMAP was OK. But we don't
222     // know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe.
223     if (OboeGlobals::areWorkaroundsEnabled()
224             && builder.willUseAAudio()
225             && builder.isDataCallbackSpecified()
226             && builder.getFramesPerDataCallback() != 0
227             && getSdkVersion() <= __ANDROID_API_R__) {
228         LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__);
229         childBuilder.setFramesPerCallback(oboe::Unspecified);
230         conversionNeeded = true;
231     }
232 
233     // If a SAMPLE RATE is specified for low latency, let the native code choose an optimal rate.
234     // This isn't really a workaround. It is an Oboe feature that is convenient to place here.
235     // TODO There may be a problem if the devices supports low latency
236     //      at a higher rate than the default.
237     if (builder.getSampleRate() != oboe::Unspecified
238             && builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None
239             && isLowLatency
240             ) {
241         childBuilder.setSampleRate(oboe::Unspecified); // native API decides the best sample rate
242         conversionNeeded = true;
243     }
244 
245     // Data Format
246     // OpenSL ES and AAudio before P do not support FAST path for FLOAT capture.
247     if (OboeGlobals::areWorkaroundsEnabled()
248             && isFloat
249             && isInput
250             && builder.isFormatConversionAllowed()
251             && isLowLatency
252             && (!builder.willUseAAudio() || (getSdkVersion() < __ANDROID_API_P__))
253             ) {
254         childBuilder.setFormat(AudioFormat::I16); // needed for FAST track
255         conversionNeeded = true;
256         LOGI("QuirksManager::%s() forcing internal format to I16 for low latency", __func__);
257     }
258 
259     // Add quirk for float output when needed.
260     if (OboeGlobals::areWorkaroundsEnabled()
261             && isFloat
262             && !isInput
263             && builder.isFormatConversionAllowed()
264             && mDeviceQuirks->shouldConvertFloatToI16ForOutputStreams()
265             ) {
266         childBuilder.setFormat(AudioFormat::I16);
267         conversionNeeded = true;
268         LOGI("QuirksManager::%s() float was requested but not supported on pre-L devices "
269              "and some devices like Vivo devices may have issues on L devices, "
270              "creating an underlying I16 stream and using format conversion to provide a float "
271              "stream", __func__);
272     }
273 
274     // Channel Count conversions
275     if (OboeGlobals::areWorkaroundsEnabled()
276             && builder.isChannelConversionAllowed()
277             && builder.getChannelCount() == kChannelCountStereo
278             && isInput
279             && isLowLatency
280             && (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))
281             ) {
282         // Workaround for heap size regression in O.
283         // b/66967812 AudioRecord does not allow FAST track for stereo capture in O
284         childBuilder.setChannelCount(kChannelCountMono);
285         conversionNeeded = true;
286         LOGI("QuirksManager::%s() using mono internally for low latency on O", __func__);
287     } else if (OboeGlobals::areWorkaroundsEnabled()
288                && builder.getChannelCount() == kChannelCountMono
289                && isInput
290                && mDeviceQuirks->isMonoMMapActuallyStereo()
291                && builder.willUseAAudio()
292                // Note: we might use this workaround on a device that supports
293                // MMAP but will use Legacy for this stream.  But this will only happen
294                // on devices that have the broken mono.
295                && mDeviceQuirks->isAAudioMMapPossible(builder)
296                ) {
297         // Workaround for mono actually running in stereo mode.
298         childBuilder.setChannelCount(kChannelCountStereo); // Use stereo and extract first channel.
299         conversionNeeded = true;
300         LOGI("QuirksManager::%s() using stereo internally to avoid broken mono", __func__);
301     }
302     // Note that MMAP does not support mono in 8.1. But that would only matter on Pixel 1
303     // phones and they have almost all been updated to 9.0.
304 
305     return conversionNeeded;
306 }
307 
isMMapSafe(AudioStreamBuilder & builder)308 bool QuirksManager::isMMapSafe(AudioStreamBuilder &builder) {
309     if (!OboeGlobals::areWorkaroundsEnabled()) return true;
310     return mDeviceQuirks->isMMapSafe(builder);
311 }
312