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 
204     // There are multiple bugs involving using callback with a specified callback size.
205     // Issue #778: O to Q had a problem with Legacy INPUT streams for FLOAT streams
206     // and a specified callback size. It would assert because of a bad buffer size.
207     //
208     // Issue #973: O to R had a problem with Legacy output streams using callback and a specified callback size.
209     // An AudioTrack stream could still be running when the AAudio FixedBlockReader was closed.
210     // Internally b/161914201#comment25
211     //
212     // Issue #983: O to R would glitch if the framesPerCallback was too small.
213     //
214     // Most of these problems were related to Legacy stream. MMAP was OK. But we don't
215     // know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe.
216     if (OboeGlobals::areWorkaroundsEnabled()
217             && builder.willUseAAudio()
218             && builder.isDataCallbackSpecified()
219             && builder.getFramesPerDataCallback() != 0
220             && getSdkVersion() <= __ANDROID_API_R__) {
221         LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__);
222         childBuilder.setFramesPerCallback(oboe::Unspecified);
223         conversionNeeded = true;
224     }
225 
226     // If a SAMPLE RATE is specified for low latency, let the native code choose an optimal rate.
227     // This isn't really a workaround. It is an Oboe feature that is convenient to place here.
228     // TODO There may be a problem if the devices supports low latency
229     //      at a higher rate than the default.
230     if (builder.getSampleRate() != oboe::Unspecified
231             && builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None
232             && isLowLatency
233             ) {
234         childBuilder.setSampleRate(oboe::Unspecified); // native API decides the best sample rate
235         conversionNeeded = true;
236     }
237 
238     // Data Format
239     // OpenSL ES and AAudio before P do not support FAST path for FLOAT capture.
240     if (OboeGlobals::areWorkaroundsEnabled()
241             && isFloat
242             && isInput
243             && builder.isFormatConversionAllowed()
244             && isLowLatency
245             && (!builder.willUseAAudio() || (getSdkVersion() < __ANDROID_API_P__))
246             ) {
247         childBuilder.setFormat(AudioFormat::I16); // needed for FAST track
248         conversionNeeded = true;
249         LOGI("QuirksManager::%s() forcing internal format to I16 for low latency", __func__);
250     }
251 
252     // Add quirk for float output when needed.
253     if (OboeGlobals::areWorkaroundsEnabled()
254             && isFloat
255             && !isInput
256             && builder.isFormatConversionAllowed()
257             && mDeviceQuirks->shouldConvertFloatToI16ForOutputStreams()
258             ) {
259         childBuilder.setFormat(AudioFormat::I16);
260         conversionNeeded = true;
261         LOGI("QuirksManager::%s() float was requested but not supported on pre-L devices "
262              "and some devices like Vivo devices may have issues on L devices, "
263              "creating an underlying I16 stream and using format conversion to provide a float "
264              "stream", __func__);
265     }
266 
267     // Channel Count conversions
268     if (OboeGlobals::areWorkaroundsEnabled()
269             && builder.isChannelConversionAllowed()
270             && builder.getChannelCount() == kChannelCountStereo
271             && isInput
272             && isLowLatency
273             && (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))
274             ) {
275         // Workaround for heap size regression in O.
276         // b/66967812 AudioRecord does not allow FAST track for stereo capture in O
277         childBuilder.setChannelCount(kChannelCountMono);
278         conversionNeeded = true;
279         LOGI("QuirksManager::%s() using mono internally for low latency on O", __func__);
280     } else if (OboeGlobals::areWorkaroundsEnabled()
281                && builder.getChannelCount() == kChannelCountMono
282                && isInput
283                && mDeviceQuirks->isMonoMMapActuallyStereo()
284                && builder.willUseAAudio()
285                // Note: we might use this workaround on a device that supports
286                // MMAP but will use Legacy for this stream.  But this will only happen
287                // on devices that have the broken mono.
288                && mDeviceQuirks->isAAudioMMapPossible(builder)
289                ) {
290         // Workaround for mono actually running in stereo mode.
291         childBuilder.setChannelCount(kChannelCountStereo); // Use stereo and extract first channel.
292         conversionNeeded = true;
293         LOGI("QuirksManager::%s() using stereo internally to avoid broken mono", __func__);
294     }
295     // Note that MMAP does not support mono in 8.1. But that would only matter on Pixel 1
296     // phones and they have almost all been updated to 9.0.
297 
298     return conversionNeeded;
299 }
300 
isMMapSafe(AudioStreamBuilder & builder)301 bool QuirksManager::isMMapSafe(AudioStreamBuilder &builder) {
302     if (!OboeGlobals::areWorkaroundsEnabled()) return true;
303     return mDeviceQuirks->isMMapSafe(builder);
304 }
305