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