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