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
65 class SamsungDeviceQuirks : public QuirksManager::DeviceQuirks {
66 public:
SamsungDeviceQuirks()67 SamsungDeviceQuirks() {
68 std::string arch = getPropertyString("ro.arch");
69 isExynos = (arch.rfind("exynos", 0) == 0); // starts with?
70
71 std::string chipname = getPropertyString("ro.hardware.chipname");
72 isExynos9810 = (chipname == "exynos9810");
73 isExynos990 = (chipname == "exynos990");
74
75 mBuildChangelist = getPropertyInteger("ro.build.changelist", 0);
76 }
77
78 virtual ~SamsungDeviceQuirks() = default;
79
getExclusiveBottomMarginInBursts() const80 int32_t getExclusiveBottomMarginInBursts() const override {
81 // TODO Make this conditional on build version when MMAP timing improves.
82 return isExynos ? kBottomMarginExynos : kBottomMarginOther;
83 }
84
getExclusiveTopMarginInBursts() const85 int32_t getExclusiveTopMarginInBursts() const override {
86 return kTopMargin;
87 }
88
89 // See Oboe issue #824 for more information.
isMonoMMapActuallyStereo() const90 bool isMonoMMapActuallyStereo() const override {
91 return isExynos9810; // TODO We can make this version specific if it gets fixed.
92 }
93
isAAudioMMapPossible(const AudioStreamBuilder & builder) const94 bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const override {
95 return DeviceQuirks::isAAudioMMapPossible(builder)
96 // Samsung says they use Legacy for Camcorder
97 && builder.getInputPreset() != oboe::InputPreset::Camcorder;
98 }
99
isMMapSafe(const AudioStreamBuilder & builder)100 bool isMMapSafe(const AudioStreamBuilder &builder) override {
101 const bool isInput = builder.getDirection() == Direction::Input;
102 // This detects b/159066712 , S20 LSI has corrupt low latency audio recording
103 // and turns off MMAP.
104 // See also https://github.com/google/oboe/issues/892
105 bool mRecordingCorrupted = isInput
106 && isExynos990
107 && mBuildChangelist < 19350896;
108 return !mRecordingCorrupted;
109 }
110
111 private:
112 // Stay farther away from DSP position on Exynos devices.
113 static constexpr int32_t kBottomMarginExynos = 2;
114 static constexpr int32_t kBottomMarginOther = 1;
115 static constexpr int32_t kTopMargin = 1;
116 bool isExynos = false;
117 bool isExynos9810 = false;
118 bool isExynos990 = false;
119 int mBuildChangelist = 0;
120 };
121
QuirksManager()122 QuirksManager::QuirksManager() {
123 std::string manufacturer = getPropertyString("ro.product.manufacturer");
124 if (manufacturer == "samsung") {
125 mDeviceQuirks = std::make_unique<SamsungDeviceQuirks>();
126 } else {
127 mDeviceQuirks = std::make_unique<DeviceQuirks>();
128 }
129 }
130
isConversionNeeded(const AudioStreamBuilder & builder,AudioStreamBuilder & childBuilder)131 bool QuirksManager::isConversionNeeded(
132 const AudioStreamBuilder &builder,
133 AudioStreamBuilder &childBuilder) {
134 bool conversionNeeded = false;
135 const bool isLowLatency = builder.getPerformanceMode() == PerformanceMode::LowLatency;
136 const bool isInput = builder.getDirection() == Direction::Input;
137 const bool isFloat = builder.getFormat() == AudioFormat::Float;
138
139 // There are multiple bugs involving using callback with a specified callback size.
140 // Issue #778: O to Q had a problem with Legacy INPUT streams for FLOAT streams
141 // and a specified callback size. It would assert because of a bad buffer size.
142 //
143 // Issue #973: O to R had a problem with Legacy output streams using callback and a specified callback size.
144 // An AudioTrack stream could still be running when the AAudio FixedBlockReader was closed.
145 // Internally b/161914201#comment25
146 //
147 // Issue #983: O to R would glitch if the framesPerCallback was too small.
148 //
149 // Most of these problems were related to Legacy stream. MMAP was OK. But we don't
150 // know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe.
151 if (OboeGlobals::areWorkaroundsEnabled()
152 && builder.willUseAAudio()
153 && builder.isDataCallbackSpecified()
154 && builder.getFramesPerDataCallback() != 0
155 && getSdkVersion() <= __ANDROID_API_R__) {
156 LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__);
157 childBuilder.setFramesPerCallback(oboe::Unspecified);
158 conversionNeeded = true;
159 }
160
161 // If a SAMPLE RATE is specified for low latency then let the native code choose an optimal rate.
162 // TODO There may be a problem if the devices supports low latency
163 // at a higher rate than the default.
164 if (builder.getSampleRate() != oboe::Unspecified
165 && builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None
166 && isLowLatency
167 ) {
168 childBuilder.setSampleRate(oboe::Unspecified); // native API decides the best sample rate
169 conversionNeeded = true;
170 }
171
172 // Data Format
173 // OpenSL ES and AAudio before P do not support FAST path for FLOAT capture.
174 if (isFloat
175 && isInput
176 && builder.isFormatConversionAllowed()
177 && isLowLatency
178 && (!builder.willUseAAudio() || (getSdkVersion() < __ANDROID_API_P__))
179 ) {
180 childBuilder.setFormat(AudioFormat::I16); // needed for FAST track
181 conversionNeeded = true;
182 LOGI("QuirksManager::%s() forcing internal format to I16 for low latency", __func__);
183 }
184
185 // Channel Count conversions
186 if (OboeGlobals::areWorkaroundsEnabled()
187 && builder.isChannelConversionAllowed()
188 && builder.getChannelCount() == kChannelCountStereo
189 && isInput
190 && isLowLatency
191 && (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))
192 ) {
193 // Workaround for heap size regression in O.
194 // b/66967812 AudioRecord does not allow FAST track for stereo capture in O
195 childBuilder.setChannelCount(kChannelCountMono);
196 conversionNeeded = true;
197 LOGI("QuirksManager::%s() using mono internally for low latency on O", __func__);
198 } else if (OboeGlobals::areWorkaroundsEnabled()
199 && builder.getChannelCount() == kChannelCountMono
200 && isInput
201 && mDeviceQuirks->isMonoMMapActuallyStereo()
202 && builder.willUseAAudio()
203 // Note: we might use this workaround on a device that supports
204 // MMAP but will use Legacy for this stream. But this will only happen
205 // on devices that have the broken mono.
206 && mDeviceQuirks->isAAudioMMapPossible(builder)
207 ) {
208 // Workaround for mono actually running in stereo mode.
209 childBuilder.setChannelCount(kChannelCountStereo); // Use stereo and extract first channel.
210 conversionNeeded = true;
211 LOGI("QuirksManager::%s() using stereo internally to avoid broken mono", __func__);
212 }
213 // Note that MMAP does not support mono in 8.1. But that would only matter on Pixel 1
214 // phones and they have almost all been updated to 9.0.
215
216 return conversionNeeded;
217 }
218
isMMapSafe(AudioStreamBuilder & builder)219 bool QuirksManager::isMMapSafe(AudioStreamBuilder &builder) {
220 if (!OboeGlobals::areWorkaroundsEnabled()) return true;
221 return mDeviceQuirks->isMMapSafe(builder);
222 }
223