1 /*
2  * Copyright 2020 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 package org.hyphonate.megaaudio.common;
17 
18 import android.content.Context;
19 import android.media.AudioFormat;
20 import android.media.AudioManager;
21 import android.util.Log;
22 
23 // For initialization
24 import org.hyphonate.megaaudio.player.JavaSourceProxy;
25 
26 /**
27  * Common base class for all audio streams.
28  */
29 public abstract class StreamBase {
30     @SuppressWarnings("unused")
31     private static final String TAG = StreamBase.class.getSimpleName();
32     @SuppressWarnings("unused")
33     private static final boolean LOG = true;
34 
35     static {
36         if (LOG) {
Log.d(TAG, "Loading MegaAudio Library...")37             Log.d(TAG, "Loading MegaAudio Library...");
38         }
39         try {
40             System.loadLibrary("megaaudio_jni");
JavaSourceProxy.initN()41             JavaSourceProxy.initN();
42         } catch (UnsatisfiedLinkError e) {
43             Log.e(TAG, "Error loading MegaAudio JNI library");
44             Log.e(TAG, "e: " + e);
45             e.printStackTrace();
46         }
47 
48         /* TODO: gracefully fail/notify if the library can't be loaded */
49     }
50 
51     //
52     // Error Codes
53     // These values must be kept in sync with the equivalent symbols in
54     // megaaudio/common/Streambase.h
55     //
56     public static final int OK = 0;
57     public static final int ERROR_UNKNOWN = -1;
58     public static final int ERROR_UNSUPPORTED = -2;
59     public static final int ERROR_INVALID_STATE = -3;
60     public static final int ERROR_DISCONNECTED = -899; // must match Oboe
61     public static final int ERROR_INVALIDSTATE = -895;
62 
63     //
64     // System Attributes
65     //
66     /**
67      * The size of the system "burst" buffer in frames.
68      * Note: Apps need to call calcNumBurstFrames(Context) to initialize this
69      * with the actual value for the system. 512 is an arbitrary, but safe value.
70      */
71     private static int sSystemBurstFrames = 512;
72 
73     /**
74      * The Preferred system sample rate.
75      */
76     private static int sSystemSampleRate = 48000;
77 
78     //
79     // Stream attributes
80     //
81     /**
82      * The number of channels requested for this stream.
83      */
84     protected int mChannelCount;
85 
86     protected int mChannelMask;
87 
88     /**
89      * The sample rate for this stream
90      */
91     protected int mSampleRate;
92 
93     /**
94      * The number of frames exchanged between the stream and the AudioSink/AudioSource.
95      * It is not (necessarily) the number of frames exchange with the OS player/recorder.
96      */
97     protected int mNumExchangeFrames;
98 
99     /**
100      * The performance mode for this stream.
101      * See Performance Mode Constants in Builder class.
102      */
103     protected int mPerformanceMode;
104 
105     /**
106      * The sharing mode for this stream. See Sharing Mode Constants in Builder class.
107      */
108     protected int mSharingMode;
109 
110     /**
111      * @return the sharing mode for the (open) stream
112      */
getSharingMode()113     public abstract int getSharingMode();
114 
115     //TODO - Add methods for changing the routing of an instantiated stream.
116 
117     // the thread on which the underlying Android AudioTrack/AudioRecord will run
118     protected Thread mStreamThread = null;
119 
120     //
121     // Initialization
122     //
123 
124     /**
125      * Forces the load of the MegaAudio (native) library
126      */
loadMegaAudioLibrary()127     public static void loadMegaAudioLibrary() {
128         // NOP. This will force the static load
129     }
130 
131     /**
132      * Performs initialization. MUST be called before any Streams are created.
133      * @param context
134      */
setup(Context context)135     public static void setup(Context context) {
136         calcNumBurstFrames(context);
137         calcSystemSampleRate(context);
138     }
139 
140     //
141     // Attributes
142     //
143 
144     /**
145      * @return The sample rate for this stream.
146      */
getSampleRate()147     public int getSampleRate() { return mSampleRate; }
148 
149     /**
150      * Gets the system-specified burst-size in frames. This should be called by the
151      * app in initialization before calling getSystemBurstFrames() (below).
152      * @return the system-specified burst size in frames.
153      */
calcNumBurstFrames(Context context)154     public static int calcNumBurstFrames(Context context) {
155         AudioManager audioManager = context.getSystemService(AudioManager.class);
156         String text = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
157         sSystemBurstFrames = Integer.parseInt(text);
158         if (LOG) {
159             Log.d(TAG, "sSystemBurstFrames:" + sSystemBurstFrames);
160         }
161         return sSystemBurstFrames;
162     }
163 
164     /**
165      * @return the system-specified burst size in frames.
166      */
getSystemBurstFrames()167     public static int getSystemBurstFrames() {
168         return sSystemBurstFrames;
169     }
170 
171     /**
172      * @param api Specifies which API BuilderBase.TYPE_NONE, BuilderBase.TYPE_JAVA
173      * or BuilderBase.TYPE_OBOE
174      * @return The optimal capacity for a stream buffer of the specified type.
175      */
getNumBurstFrames(int api)176     public static int getNumBurstFrames(int api) {
177         return sSystemBurstFrames;
178     }
179 
180     /**
181      *
182      */
getNumExchangeFrames()183     public int getNumExchangeFrames() {
184         return mNumExchangeFrames;
185     }
186 
187     /**
188      * Gets the system-speficied preferred sample rate for audio. This should be called by the
189      *      * app in initialization before calling getSystemSampleRate() (below).
190      * @return the system preferred sample rate
191      */
calcSystemSampleRate(Context context)192     public static int calcSystemSampleRate(Context context) {
193         AudioManager audioManager = context.getSystemService(AudioManager.class);
194         String text = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
195         return sSystemSampleRate = Integer.parseInt(text);
196     }
197 
198     /**
199      * @return the system preferred sample rate
200      */
getSystemSampleRate()201     public static int getSystemSampleRate() {
202         return sSystemSampleRate;
203     }
204 
205     // Routing
getRoutedDeviceId()206     public abstract int getRoutedDeviceId();
207 
208     //
209     // Sample Format Utils
210     //
211     /**
212      * @param encoding An Android ENCODING_ constant for audio data.
213      * @return The size in BYTES of samples encoded as specified.
214      */
sampleSizeInBytes(int encoding)215     public static int sampleSizeInBytes(int encoding) {
216         switch (encoding) {
217             case AudioFormat.ENCODING_PCM_16BIT:
218                 return 2;
219 
220             case AudioFormat.ENCODING_PCM_FLOAT:
221                 return 4;
222 
223             default:
224                 return 0;
225         }
226     }
227 
228     //
229     // State
230     //
231     /**
232      * Releases resources used by the stream.
233      * @return
234      */
teardownStream()235     public abstract int teardownStream();
236 
237     /**
238      * Starts playback on an open stream player. (@see open() method above).
239      * @return              ERROR_NONE if successful, otherwise an error code
240      */
startStream()241     public abstract int startStream();
242 
243     /**
244      * Stops playback.
245      * May not stop the stream immediately. i.e. does not stop until the next audio callback
246      * from the underlying system.
247      * @return              ERROR_NONE if successful, otherwise an error code
248      */
stopStream()249     public abstract int stopStream();
250 
251     /**
252      * @return See StreamState constants
253      */
getStreamState()254     public abstract int getStreamState();
255 
256     /**
257      * @return the ACTUAL number of channels in this stream
258      * (as opposed to the number requested).
259      * -1 if there is no valid stream.
260      */
getChannelCount()261     public abstract int getChannelCount();
262 
263     /**
264      * Note: The stream must be created before calling this method.
265      * @return true if the underlying stream is an MMAP stream, false otherwise.
266      */
isMMap()267     public abstract boolean isMMap();
268 
269     /**
270      * @return The last error callback result (these must match Oboe). See Oboe constants
271      */
getLastErrorCallbackResult()272     public abstract int getLastErrorCallbackResult();
273 
274     //
275     // Thread stuff
276     //
277     /**
278      * Joins the record thread to ensure that the stream is stopped.
279      */
waitForStreamThreadToExit()280     protected void waitForStreamThreadToExit() {
281         try {
282             if (mStreamThread != null) {
283                 mStreamThread.join();
284                 mStreamThread = null;
285             }
286         } catch (InterruptedException e) {
287             e.printStackTrace();
288         }
289     }
290 
291     //
292     // Utility
293     //
294     /**
295      * @param chanCount The number of channels for which to generate an index mask.
296      * @return  A channel index mask corresponding to the supplied channel count.
297      *
298      * note: The generated index mask has active channels from 0 to chanCount - 1
299      */
channelCountToIndexMask(int chanCount)300     public static int channelCountToIndexMask(int chanCount) {
301         return  (1 << chanCount) - 1;
302     }
303 
304     private static int[] sOutMasks =
305             {   -1,
306                 AudioFormat.CHANNEL_OUT_MONO,
307                 AudioFormat.CHANNEL_OUT_STEREO,
308                 AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER,
309                 AudioFormat.CHANNEL_OUT_QUAD
310             };
311 
312     /**
313      *
314      * @param chanCount The number of channels for which to generate a postional mask.
315      * @return the corresponding channel position mask
316      * note: This mapping is not well defined, but may be needed to get a fast path in the Java API
317      */
channelCountToOutPositionMask(int chanCount)318     public static int channelCountToOutPositionMask(int chanCount) {
319         return chanCount <= 4 ? sOutMasks[chanCount] : AudioFormat.CHANNEL_OUT_STEREO;
320     }
321 }
322