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 #ifndef __ANDROID_API_R__
24 #define __ANDROID_API_R__ 30
25 #endif
26
27 using namespace oboe;
28
clipBufferSize(AudioStream & stream,int32_t requestedSize)29 int32_t QuirksManager::DeviceQuirks::clipBufferSize(AudioStream &stream,
30 int32_t requestedSize) {
31 if (!OboeGlobals::areWorkaroundsEnabled()) {
32 return requestedSize;
33 }
34 int bottomMargin = kDefaultBottomMarginInBursts;
35 int topMargin = kDefaultTopMarginInBursts;
36 if (isMMapUsed(stream)) {
37 if (stream.getSharingMode() == SharingMode::Exclusive) {
38 bottomMargin = getExclusiveBottomMarginInBursts();
39 topMargin = getExclusiveTopMarginInBursts();
40 }
41 } else {
42 bottomMargin = kLegacyBottomMarginInBursts;
43 }
44
45 int32_t burst = stream.getFramesPerBurst();
46 int32_t minSize = bottomMargin * burst;
47 int32_t adjustedSize = requestedSize;
48 if (adjustedSize < minSize ) {
49 adjustedSize = minSize;
50 } else {
51 int32_t maxSize = stream.getBufferCapacityInFrames() - (topMargin * burst);
52 if (adjustedSize > maxSize ) {
53 adjustedSize = maxSize;
54 }
55 }
56 return adjustedSize;
57 }
58
isAAudioMMapPossible(const AudioStreamBuilder & builder) const59 bool QuirksManager::DeviceQuirks::isAAudioMMapPossible(const AudioStreamBuilder &builder) const {
60 bool isSampleRateCompatible =
61 builder.getSampleRate() == oboe::Unspecified
62 || builder.getSampleRate() == kCommonNativeRate
63 || builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None;
64 return builder.getPerformanceMode() == PerformanceMode::LowLatency
65 && isSampleRateCompatible
66 && builder.getChannelCount() <= kChannelCountStereo;
67 }
68
69 class SamsungDeviceQuirks : public QuirksManager::DeviceQuirks {
70 public:
SamsungDeviceQuirks()71 SamsungDeviceQuirks() {
72 std::string arch = getPropertyString("ro.arch");
73 isExynos = (arch.rfind("exynos", 0) == 0); // starts with?
74
75 std::string chipname = getPropertyString("ro.hardware.chipname");
76 isExynos9810 = (chipname == "exynos9810");
77 }
78
79 virtual ~SamsungDeviceQuirks() = default;
80
getExclusiveBottomMarginInBursts() const81 int32_t getExclusiveBottomMarginInBursts() const override {
82 // TODO Make this conditional on build version when MMAP timing improves.
83 return isExynos ? kBottomMarginExynos : kBottomMarginOther;
84 }
85
getExclusiveTopMarginInBursts() const86 int32_t getExclusiveTopMarginInBursts() const override {
87 return kTopMargin;
88 }
89
90 // See Oboe issue #824 for more information.
isMonoMMapActuallyStereo() const91 bool isMonoMMapActuallyStereo() const override {
92 return isExynos9810; // TODO We can make this version specific if it gets fixed.
93 }
94
isAAudioMMapPossible(const AudioStreamBuilder & builder) const95 bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const override {
96 return DeviceQuirks::isAAudioMMapPossible(builder)
97 // Samsung says they use Legacy for Camcorder
98 && builder.getInputPreset() != oboe::InputPreset::Camcorder;
99 }
100
101 private:
102 // Stay farther away from DSP position on Exynos devices.
103 static constexpr int32_t kBottomMarginExynos = 2;
104 static constexpr int32_t kBottomMarginOther = 1;
105 static constexpr int32_t kTopMargin = 1;
106 bool isExynos = false;
107 bool isExynos9810 = false;
108 };
109
QuirksManager()110 QuirksManager::QuirksManager() {
111 std::string manufacturer = getPropertyString("ro.product.manufacturer");
112 if (manufacturer == "samsung") {
113 mDeviceQuirks = std::make_unique<SamsungDeviceQuirks>();
114 } else {
115 mDeviceQuirks = std::make_unique<DeviceQuirks>();
116 }
117 }
118
isConversionNeeded(const AudioStreamBuilder & builder,AudioStreamBuilder & childBuilder)119 bool QuirksManager::isConversionNeeded(
120 const AudioStreamBuilder &builder,
121 AudioStreamBuilder &childBuilder) {
122 bool conversionNeeded = false;
123 const bool isLowLatency = builder.getPerformanceMode() == PerformanceMode::LowLatency;
124 const bool isInput = builder.getDirection() == Direction::Input;
125 const bool isFloat = builder.getFormat() == AudioFormat::Float;
126
127 // There are multiple bugs involving using callback with a specified callback size.
128 // Issue #778: O to Q had a problem with Legacy INPUT streams for FLOAT streams
129 // and a specified callback size. It would assert because of a bad buffer size.
130 //
131 // Issue #973: O to R had a problem with Legacy output streams using callback and a specified callback size.
132 // An AudioTrack stream could still be running when the AAudio FixedBlockReader was closed.
133 // Internally b/161914201#comment25
134 //
135 // Issue #983: O to R would glitch if the framesPerCallback was too small.
136 //
137 // Most of these problems were related to Legacy stream. MMAP was OK. But we don't
138 // know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe.
139 if (OboeGlobals::areWorkaroundsEnabled()
140 && builder.willUseAAudio()
141 && builder.getCallback() != nullptr
142 && builder.getFramesPerCallback() != 0
143 && getSdkVersion() <= __ANDROID_API_R__) {
144 LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__);
145 childBuilder.setFramesPerCallback(oboe::Unspecified);
146 conversionNeeded = true;
147 }
148
149 // If a SAMPLE RATE is specified for low latency then let the native code choose an optimal rate.
150 // TODO There may be a problem if the devices supports low latency
151 // at a higher rate than the default.
152 if (builder.getSampleRate() != oboe::Unspecified
153 && builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None
154 && isLowLatency
155 ) {
156 childBuilder.setSampleRate(oboe::Unspecified); // native API decides the best sample rate
157 conversionNeeded = true;
158 }
159
160 // Data Format
161 // OpenSL ES and AAudio before P do not support FAST path for FLOAT capture.
162 if (isFloat
163 && isInput
164 && builder.isFormatConversionAllowed()
165 && isLowLatency
166 && (!builder.willUseAAudio() || (getSdkVersion() < __ANDROID_API_P__))
167 ) {
168 childBuilder.setFormat(AudioFormat::I16); // needed for FAST track
169 conversionNeeded = true;
170 LOGI("QuirksManager::%s() forcing internal format to I16 for low latency", __func__);
171 }
172
173 // Channel Count conversions
174 if (OboeGlobals::areWorkaroundsEnabled()
175 && builder.isChannelConversionAllowed()
176 && builder.getChannelCount() == kChannelCountStereo
177 && isInput
178 && isLowLatency
179 && (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))
180 ) {
181 // Workaround for heap size regression in O.
182 // b/66967812 AudioRecord does not allow FAST track for stereo capture in O
183 childBuilder.setChannelCount(kChannelCountMono);
184 conversionNeeded = true;
185 LOGI("QuirksManager::%s() using mono internally for low latency on O", __func__);
186 } else if (OboeGlobals::areWorkaroundsEnabled()
187 && builder.getChannelCount() == kChannelCountMono
188 && isInput
189 && mDeviceQuirks->isMonoMMapActuallyStereo()
190 && builder.willUseAAudio()
191 // Note: we might use this workaround on a device that supports
192 // MMAP but will use Legacy for this stream. But this will only happen
193 // on devices that have the broken mono.
194 && mDeviceQuirks->isAAudioMMapPossible(builder)
195 ) {
196 // Workaround for mono actually running in stereo mode.
197 childBuilder.setChannelCount(kChannelCountStereo); // Use stereo and extract first channel.
198 conversionNeeded = true;
199 LOGI("QuirksManager::%s() using stereo internally to avoid broken mono", __func__);
200 }
201 // Note that MMAP does not support mono in 8.1. But that would only matter on Pixel 1
202 // phones and they have almost all been updated to 9.0.
203
204 return conversionNeeded;
205 }
206