1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 //==============================================================================
27 /**
28     Some shared helpers methods for using the high-performance audio paths on
29     Android devices (OpenSL and Oboe).
30 
31     @tags{Audio}
32 */
33 namespace AndroidHighPerformanceAudioHelpers
34 {
35     //==============================================================================
getNativeSampleRate()36     static double getNativeSampleRate()
37     {
38         return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue();
39     }
40 
getNativeBufferSizeHint()41     static int getNativeBufferSizeHint()
42     {
43         // This property is a hint of a native buffer size but it does not guarantee the size used.
44         auto deviceBufferSize = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue();
45 
46         if (deviceBufferSize == 0)
47             return 192;
48 
49         return deviceBufferSize;
50     }
51 
isProAudioDevice()52     static bool isProAudioDevice()
53     {
54         static bool isSapaSupported = SystemStats::getDeviceManufacturer().containsIgnoreCase ("SAMSUNG")
55                                      && DynamicLibrary().open ("libapa_jni.so");
56 
57         return androidHasSystemFeature ("android.hardware.audio.pro") || isSapaSupported;
58     }
59 
hasLowLatencyAudioPath()60     static bool hasLowLatencyAudioPath()
61     {
62         return androidHasSystemFeature ("android.hardware.audio.low_latency");
63     }
64 
canUseHighPerformanceAudioPath(int nativeBufferSize,int requestedBufferSize,int requestedSampleRate)65     static bool canUseHighPerformanceAudioPath (int nativeBufferSize, int requestedBufferSize, int requestedSampleRate)
66     {
67         return ((requestedBufferSize % nativeBufferSize) == 0)
68                && (requestedSampleRate == getNativeSampleRate())
69                && isProAudioDevice();
70     }
71 
72     //==============================================================================
getMinimumBuffersToEnqueue(int nativeBufferSize,double requestedSampleRate)73     static int getMinimumBuffersToEnqueue (int nativeBufferSize, double requestedSampleRate)
74     {
75         if (canUseHighPerformanceAudioPath (nativeBufferSize, nativeBufferSize, (int) requestedSampleRate))
76         {
77             // see https://developer.android.com/ndk/guides/audio/opensl/opensl-prog-notes.html#sandp
78             // "For Android 4.2 (API level 17) and earlier, a buffer count of two or more is required
79             //  for lower latency. Beginning with Android 4.3 (API level 18), a buffer count of one
80             //  is sufficient for lower latency."
81             return (getAndroidSDKVersion() >= 18 ? 1 : 2);
82         }
83 
84         // not using low-latency path so we can use the absolute minimum number of buffers to queue
85         return 1;
86     }
87 
buffersToQueueForBufferDuration(int nativeBufferSize,int bufferDurationInMs,double sampleRate)88     static int buffersToQueueForBufferDuration (int nativeBufferSize, int bufferDurationInMs, double sampleRate) noexcept
89     {
90         auto maxBufferFrames = static_cast<int> (std::ceil (bufferDurationInMs * sampleRate / 1000.0));
91         auto maxNumBuffers   = static_cast<int> (std::ceil (static_cast<double> (maxBufferFrames)
92                                                   / static_cast<double> (nativeBufferSize)));
93 
94         return jmax (getMinimumBuffersToEnqueue (nativeBufferSize, sampleRate), maxNumBuffers);
95     }
96 
getMaximumBuffersToEnqueue(int nativeBufferSize,double maximumSampleRate)97     static int getMaximumBuffersToEnqueue (int nativeBufferSize, double maximumSampleRate) noexcept
98     {
99         static constexpr int maxBufferSizeMs = 200;
100 
101         return jmax (8, buffersToQueueForBufferDuration (nativeBufferSize, maxBufferSizeMs, maximumSampleRate));
102     }
103 
getAvailableBufferSizes(int nativeBufferSize,Array<double> availableSampleRates)104     static Array<int> getAvailableBufferSizes (int nativeBufferSize, Array<double> availableSampleRates)
105     {
106         auto minBuffersToQueue = getMinimumBuffersToEnqueue (nativeBufferSize, getNativeSampleRate());
107         auto maxBuffersToQueue = getMaximumBuffersToEnqueue (nativeBufferSize, findMaximum (availableSampleRates.getRawDataPointer(),
108                                                                                             availableSampleRates.size()));
109 
110         Array<int> bufferSizes;
111 
112         for (int i = minBuffersToQueue; i <= maxBuffersToQueue; ++i)
113             bufferSizes.add (i * nativeBufferSize);
114 
115         return bufferSizes;
116     }
117 
getDefaultBufferSize(int nativeBufferSize,double currentSampleRate)118     static int getDefaultBufferSize (int nativeBufferSize, double currentSampleRate)
119     {
120         static constexpr int defaultBufferSizeForLowLatencyDeviceMs = 40;
121         static constexpr int defaultBufferSizeForStandardLatencyDeviceMs = 100;
122 
123         auto defaultBufferLength = (hasLowLatencyAudioPath() ? defaultBufferSizeForLowLatencyDeviceMs
124                                                              : defaultBufferSizeForStandardLatencyDeviceMs);
125 
126         auto defaultBuffersToEnqueue = buffersToQueueForBufferDuration (nativeBufferSize, defaultBufferLength, currentSampleRate);
127         return defaultBuffersToEnqueue * nativeBufferSize;
128     }
129 }
130 
131 } // namespace juce
132