aboutsummaryrefslogtreecommitdiff
path: root/src/common/QuirksManager.cpp
blob: 16425fb0f16a22d210c363b48c0e33b59008caf8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <oboe/AudioStreamBuilder.h>
#include <oboe/Oboe.h>

#include "OboeDebug.h"
#include "QuirksManager.h"

using namespace oboe;

int32_t QuirksManager::DeviceQuirks::clipBufferSize(AudioStream &stream,
            int32_t requestedSize) {
    if (!OboeGlobals::areWorkaroundsEnabled()) {
        return requestedSize;
    }
    int bottomMargin = kDefaultBottomMarginInBursts;
    int topMargin = kDefaultTopMarginInBursts;
    if (isMMapUsed(stream)) {
        if (stream.getSharingMode() == SharingMode::Exclusive) {
            bottomMargin = getExclusiveBottomMarginInBursts();
            topMargin = getExclusiveTopMarginInBursts();
        }
    } else {
        bottomMargin = kLegacyBottomMarginInBursts;
    }

    int32_t burst = stream.getFramesPerBurst();
    int32_t minSize = bottomMargin * burst;
    int32_t adjustedSize = requestedSize;
    if (adjustedSize < minSize ) {
        adjustedSize = minSize;
    } else {
        int32_t maxSize = stream.getBufferCapacityInFrames() - (topMargin * burst);
        if (adjustedSize > maxSize ) {
            adjustedSize = maxSize;
        }
    }
    return adjustedSize;
}

bool QuirksManager::DeviceQuirks::isAAudioMMapPossible(const AudioStreamBuilder &builder) const {
    bool isSampleRateCompatible =
            builder.getSampleRate() == oboe::Unspecified
            || builder.getSampleRate() == kCommonNativeRate
            || builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None;
    return builder.getPerformanceMode() == PerformanceMode::LowLatency
            && isSampleRateCompatible
            && builder.getChannelCount() <= kChannelCountStereo;
}

bool QuirksManager::DeviceQuirks::shouldConvertFloatToI16ForOutputStreams() {
    std::string productManufacturer = getPropertyString("ro.product.manufacturer");
    if (getSdkVersion() < __ANDROID_API_L__) {
        return true;
    } else if ((productManufacturer == "vivo") && (getSdkVersion() < __ANDROID_API_M__)) {
        return true;
    }
    return false;
}

/**
 * This is for Samsung Exynos quirks. Samsung Mobile uses Qualcomm chips so
 * the QualcommDeviceQuirks would apply.
 */
class SamsungExynosDeviceQuirks : public  QuirksManager::DeviceQuirks {
public:
    SamsungExynosDeviceQuirks() {
        std::string chipname = getPropertyString("ro.hardware.chipname");
        isExynos9810 = (chipname == "exynos9810");
        isExynos990 = (chipname == "exynos990");
        isExynos850 = (chipname == "exynos850");

        mBuildChangelist = getPropertyInteger("ro.build.changelist", 0);
    }

    virtual ~SamsungExynosDeviceQuirks() = default;

    int32_t getExclusiveBottomMarginInBursts() const override {
        return kBottomMargin;
    }

    int32_t getExclusiveTopMarginInBursts() const override {
        return kTopMargin;
    }

    // See Oboe issues #824 and #1247 for more information.
    bool isMonoMMapActuallyStereo() const override {
        return isExynos9810 || isExynos850; // TODO We can make this version specific if it gets fixed.
    }

    bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const override {
        return DeviceQuirks::isAAudioMMapPossible(builder)
                // Samsung says they use Legacy for Camcorder
                && builder.getInputPreset() != oboe::InputPreset::Camcorder;
    }

    bool isMMapSafe(const AudioStreamBuilder &builder) override {
        const bool isInput = builder.getDirection() == Direction::Input;
        // This detects b/159066712 , S20 LSI has corrupt low latency audio recording
        // and turns off MMAP.
        // See also https://github.com/google/oboe/issues/892
        bool isRecordingCorrupted = isInput
            && isExynos990
            && mBuildChangelist < 19350896;

        // Certain S9+ builds record silence when using MMAP and not using the VoiceCommunication
        // preset.
        // See https://github.com/google/oboe/issues/1110
        bool wouldRecordSilence = isInput
            && isExynos9810
            && mBuildChangelist <= 18847185
            && (builder.getInputPreset() != InputPreset::VoiceCommunication);

        if (wouldRecordSilence){
            LOGI("QuirksManager::%s() Requested stream configuration would result in silence on "
                 "this device. Switching off MMAP.", __func__);
        }

        return !isRecordingCorrupted && !wouldRecordSilence;
    }

private:
    // Stay farther away from DSP position on Exynos devices.
    static constexpr int32_t kBottomMargin = 2;
    static constexpr int32_t kTopMargin = 1;
    bool isExynos9810 = false;
    bool isExynos990 = false;
    bool isExynos850 = false;
    int mBuildChangelist = 0;
};

class QualcommDeviceQuirks : public  QuirksManager::DeviceQuirks {
public:
    QualcommDeviceQuirks() {
        std::string modelName = getPropertyString("ro.soc.model");
        isSM8150 = (modelName == "SDM8150");
    }

    virtual ~QualcommDeviceQuirks() = default;

    int32_t getExclusiveBottomMarginInBursts() const override {
        return kBottomMargin;
    }

    bool isMMapSafe(const AudioStreamBuilder &builder) override {
        // See https://github.com/google/oboe/issues/1121#issuecomment-897957749
        bool isMMapBroken = false;
        if (isSM8150 && (getSdkVersion() <= __ANDROID_API_P__)) {
            LOGI("QuirksManager::%s() MMAP not actually supported on this chip."
                 " Switching off MMAP.", __func__);
            isMMapBroken = true;
        }

        return !isMMapBroken;
    }

private:
    bool isSM8150 = false;
    static constexpr int32_t kBottomMargin = 1;
};

QuirksManager::QuirksManager() {
    std::string productManufacturer = getPropertyString("ro.product.manufacturer");
    if (productManufacturer == "samsung") {
        std::string arch = getPropertyString("ro.arch");
        bool isExynos = (arch.rfind("exynos", 0) == 0); // starts with?
        if (isExynos) {
            mDeviceQuirks = std::make_unique<SamsungExynosDeviceQuirks>();
        }
    }
    if (!mDeviceQuirks) {
        std::string socManufacturer = getPropertyString("ro.soc.manufacturer");
        if (socManufacturer == "Qualcomm") {
            // This may include Samsung Mobile devices.
            mDeviceQuirks = std::make_unique<QualcommDeviceQuirks>();
        } else {
            mDeviceQuirks = std::make_unique<DeviceQuirks>();
        }
    }
}

bool QuirksManager::isConversionNeeded(
        const AudioStreamBuilder &builder,
        AudioStreamBuilder &childBuilder) {
    bool conversionNeeded = false;
    const bool isLowLatency = builder.getPerformanceMode() == PerformanceMode::LowLatency;
    const bool isInput = builder.getDirection() == Direction::Input;
    const bool isFloat = builder.getFormat() == AudioFormat::Float;

    // There are multiple bugs involving using callback with a specified callback size.
    // Issue #778: O to Q had a problem with Legacy INPUT streams for FLOAT streams
    // and a specified callback size. It would assert because of a bad buffer size.
    //
    // Issue #973: O to R had a problem with Legacy output streams using callback and a specified callback size.
    // An AudioTrack stream could still be running when the AAudio FixedBlockReader was closed.
    // Internally b/161914201#comment25
    //
    // Issue #983: O to R would glitch if the framesPerCallback was too small.
    //
    // Most of these problems were related to Legacy stream. MMAP was OK. But we don't
    // know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe.
    if (OboeGlobals::areWorkaroundsEnabled()
            && builder.willUseAAudio()
            && builder.isDataCallbackSpecified()
            && builder.getFramesPerDataCallback() != 0
            && getSdkVersion() <= __ANDROID_API_R__) {
        LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__);
        childBuilder.setFramesPerCallback(oboe::Unspecified);
        conversionNeeded = true;
    }

    // If a SAMPLE RATE is specified for low latency, let the native code choose an optimal rate.
    // This isn't really a workaround. It is an Oboe feature that is convenient to place here.
    // TODO There may be a problem if the devices supports low latency
    //      at a higher rate than the default.
    if (builder.getSampleRate() != oboe::Unspecified
            && builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None
            && isLowLatency
            ) {
        childBuilder.setSampleRate(oboe::Unspecified); // native API decides the best sample rate
        conversionNeeded = true;
    }

    // Data Format
    // OpenSL ES and AAudio before P do not support FAST path for FLOAT capture.
    if (OboeGlobals::areWorkaroundsEnabled()
            && isFloat
            && isInput
            && builder.isFormatConversionAllowed()
            && isLowLatency
            && (!builder.willUseAAudio() || (getSdkVersion() < __ANDROID_API_P__))
            ) {
        childBuilder.setFormat(AudioFormat::I16); // needed for FAST track
        conversionNeeded = true;
        LOGI("QuirksManager::%s() forcing internal format to I16 for low latency", __func__);
    }

    // Add quirk for float output when needed.
    if (OboeGlobals::areWorkaroundsEnabled()
            && isFloat
            && !isInput
            && builder.isFormatConversionAllowed()
            && mDeviceQuirks->shouldConvertFloatToI16ForOutputStreams()
            ) {
        childBuilder.setFormat(AudioFormat::I16);
        conversionNeeded = true;
        LOGI("QuirksManager::%s() float was requested but not supported on pre-L devices "
             "and some devices like Vivo devices may have issues on L devices, "
             "creating an underlying I16 stream and using format conversion to provide a float "
             "stream", __func__);
    }

    // Channel Count conversions
    if (OboeGlobals::areWorkaroundsEnabled()
            && builder.isChannelConversionAllowed()
            && builder.getChannelCount() == kChannelCountStereo
            && isInput
            && isLowLatency
            && (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))
            ) {
        // Workaround for heap size regression in O.
        // b/66967812 AudioRecord does not allow FAST track for stereo capture in O
        childBuilder.setChannelCount(kChannelCountMono);
        conversionNeeded = true;
        LOGI("QuirksManager::%s() using mono internally for low latency on O", __func__);
    } else if (OboeGlobals::areWorkaroundsEnabled()
               && builder.getChannelCount() == kChannelCountMono
               && isInput
               && mDeviceQuirks->isMonoMMapActuallyStereo()
               && builder.willUseAAudio()
               // Note: we might use this workaround on a device that supports
               // MMAP but will use Legacy for this stream.  But this will only happen
               // on devices that have the broken mono.
               && mDeviceQuirks->isAAudioMMapPossible(builder)
               ) {
        // Workaround for mono actually running in stereo mode.
        childBuilder.setChannelCount(kChannelCountStereo); // Use stereo and extract first channel.
        conversionNeeded = true;
        LOGI("QuirksManager::%s() using stereo internally to avoid broken mono", __func__);
    }
    // Note that MMAP does not support mono in 8.1. But that would only matter on Pixel 1
    // phones and they have almost all been updated to 9.0.

    return conversionNeeded;
}

bool QuirksManager::isMMapSafe(AudioStreamBuilder &builder) {
    if (!OboeGlobals::areWorkaroundsEnabled()) return true;
    return mDeviceQuirks->isMMapSafe(builder);
}