• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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