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_BAD_START = 4; 61 public static final int ERROR_BAD_OPEN = 5; 62 public static final int ERROR_INVALID_ARGUMENT = 6; 63 public static final int ERROR_DISCONNECTED = -899; // must match Oboe 64 public static final int ERROR_INVALIDSTATE = -895; 65 66 // 67 // System Attributes 68 // 69 /** 70 * The size of the system "burst" buffer in frames. 71 * Note: Apps need to call calcNumBurstFrames(Context) to initialize this 72 * with the actual value for the system. 512 is an arbitrary, but safe value. 73 */ 74 private static int sSystemBurstFrames = 512; 75 76 /** 77 * The Preferred system sample rate. 78 */ 79 private static int sSystemSampleRate = 48000; 80 81 // 82 // Stream attributes 83 // 84 /** 85 * The number of channels requested for this stream. 86 */ 87 protected int mChannelCount; 88 89 protected int mChannelMask; 90 91 /** 92 * The sample rate for this stream 93 */ 94 protected int mSampleRate; 95 96 /** 97 * The number of frames exchanged between the stream and the AudioSink/AudioSource. 98 * It is not (necessarily) the number of frames exchange with the OS player/recorder. 99 */ 100 protected int mNumExchangeFrames; 101 102 /** 103 * The performance mode for this stream. 104 * See Performance Mode Constants in Builder class. 105 */ 106 protected int mPerformanceMode; 107 108 /** 109 * The sharing mode for this stream. See Sharing Mode Constants in Builder class. 110 */ 111 protected int mSharingMode; 112 113 /** 114 * @return the sharing mode for the (open) stream 115 */ getSharingMode()116 public abstract int getSharingMode(); 117 118 //TODO - Add methods for changing the routing of an instantiated stream. 119 120 // the thread on which the underlying Android AudioTrack/AudioRecord will run 121 protected Thread mStreamThread = null; 122 123 // 124 // Initialization 125 // 126 127 /** 128 * Forces the load of the MegaAudio (native) library 129 */ loadMegaAudioLibrary()130 public static void loadMegaAudioLibrary() { 131 // NOP. This will force the static load 132 } 133 134 /** 135 * Performs initialization. MUST be called before any Streams are created. 136 * @param context 137 */ setup(Context context)138 public static void setup(Context context) { 139 calcNumBurstFrames(context); 140 calcSystemSampleRate(context); 141 } 142 143 // 144 // Lifecycle 145 // 146 private static final int LIFECYCLE_NONE = 0; 147 private static final int LIFECYCLE_BUILT = 1; 148 private static final int LIFECYCLE_OPENED = 2; 149 private static final int LIFECYCLE_STARTED = 3; 150 private static final int LIFECYCLE_STOPPED = 4; 151 private static final int LIFECYCLE_CLOSED = 5; 152 private int mLifecycleStep = LIFECYCLE_NONE; 153 154 /** 155 * Builds the stream, but does not open or start it. 156 * 157 * @param builder The builder containing the attributes for the stream. 158 * @return Either StreamBase.OK or an appropriate error code. 159 */ build(BuilderBase builder)160 public abstract int build(BuilderBase builder); 161 162 /** 163 * Opens a stream in preparation for starting. 164 * @return Either StreamBase.OK or an appropriate error code. 165 */ open()166 public abstract int open(); 167 168 /** 169 * Starts the stream. 170 * @return Either StreamBase.OK or an appropriate error code. 171 */ start()172 public abstract int start(); 173 174 /** 175 * Stops the stream. 176 * @return Either StreamBase.OK or an appropriate error code. 177 */ stop()178 public abstract int stop(); 179 180 /** 181 * Closes the stream. 182 * @return Either StreamBase.OK or an appropriate error code. 183 */ close()184 public abstract int close(); 185 186 /** 187 * Tearsdown (deallocates) the stream. 188 * @return Either StreamBase.OK or an appropriate error code. 189 */ teardown()190 public abstract int teardown(); 191 trackBuild(int result)192 protected int trackBuild(int result) { 193 if (result == OK) { 194 mLifecycleStep = LIFECYCLE_BUILT; 195 } 196 return result; 197 } 198 199 // 200 // Lifecycle Monitoring 201 // trackOpen(int result)202 protected int trackOpen(int result) { 203 if (result == OK) { 204 mLifecycleStep = LIFECYCLE_OPENED; 205 } 206 return result; 207 } 208 trackStart(int result)209 protected int trackStart(int result) { 210 if (result == OK) { 211 mLifecycleStep = LIFECYCLE_STARTED; 212 } 213 return result; 214 } 215 trackStop(int result)216 protected int trackStop(int result) { 217 if (result == OK) { 218 mLifecycleStep = LIFECYCLE_STOPPED; 219 } 220 return result; 221 } 222 trackClose(int result)223 protected int trackClose(int result) { 224 if (result == OK) { 225 mLifecycleStep = LIFECYCLE_CLOSED; 226 } 227 return result; 228 } 229 trackTeardown(int result)230 protected int trackTeardown(int result) { 231 if (result == OK) { 232 mLifecycleStep = LIFECYCLE_NONE; 233 } 234 return result; 235 } 236 237 /** 238 * stops/closes/tearsdown a stream based on its current lifecycle step. 239 */ unwind()240 public void unwind() { 241 if (LOG) { 242 Log.d(TAG, "unwind() mLifecycleStep: " + mLifecycleStep); 243 } 244 switch (mLifecycleStep) { 245 case LIFECYCLE_NONE: 246 // NOP 247 break; 248 249 case LIFECYCLE_BUILT: 250 teardown(); 251 break; 252 253 case LIFECYCLE_OPENED: 254 close(); 255 teardown(); 256 break; 257 258 case LIFECYCLE_STARTED: 259 stop(); 260 close(); 261 teardown(); 262 break; 263 264 case LIFECYCLE_STOPPED: 265 close(); 266 teardown(); 267 break; 268 269 case LIFECYCLE_CLOSED: 270 teardown(); 271 break; 272 } 273 } 274 275 // 276 // Attributes 277 // 278 279 /** 280 * @return The sample rate for this stream. 281 */ getSampleRate()282 public int getSampleRate() { return mSampleRate; } 283 284 /** 285 * Gets the system-specified burst-size in frames. This should be called by the 286 * app in initialization before calling getSystemBurstFrames() (below). 287 * @return the system-specified burst size in frames. 288 */ calcNumBurstFrames(Context context)289 public static int calcNumBurstFrames(Context context) { 290 AudioManager audioManager = context.getSystemService(AudioManager.class); 291 String text = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 292 sSystemBurstFrames = Integer.parseInt(text); 293 if (LOG) { 294 Log.d(TAG, "sSystemBurstFrames:" + sSystemBurstFrames); 295 } 296 return sSystemBurstFrames; 297 } 298 299 /** 300 * @return the system-specified burst size in frames. 301 */ getSystemBurstFrames()302 public static int getSystemBurstFrames() { 303 return sSystemBurstFrames; 304 } 305 306 /** 307 * @param api Specifies which API BuilderBase.TYPE_NONE, BuilderBase.TYPE_JAVA 308 * or BuilderBase.TYPE_OBOE 309 * @return The optimal capacity for a stream buffer of the specified type. 310 */ getNumBurstFrames(int api)311 public static int getNumBurstFrames(int api) { 312 return sSystemBurstFrames; 313 } 314 315 /** 316 * 317 */ getNumExchangeFrames()318 public int getNumExchangeFrames() { 319 return mNumExchangeFrames; 320 } 321 322 /** 323 * Gets the system-speficied preferred sample rate for audio. This should be called by the 324 * * app in initialization before calling getSystemSampleRate() (below). 325 * @return the system preferred sample rate 326 */ calcSystemSampleRate(Context context)327 public static int calcSystemSampleRate(Context context) { 328 AudioManager audioManager = context.getSystemService(AudioManager.class); 329 String text = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 330 return sSystemSampleRate = Integer.parseInt(text); 331 } 332 333 /** 334 * @return the system preferred sample rate 335 */ getSystemSampleRate()336 public static int getSystemSampleRate() { 337 return sSystemSampleRate; 338 } 339 340 // Routing getRoutedDeviceId()341 public abstract int getRoutedDeviceId(); 342 343 // 344 // Sample Format Utils 345 // 346 /** 347 * @param encoding An Android ENCODING_ constant for audio data. 348 * @return The size in BYTES of samples encoded as specified. 349 */ sampleSizeInBytes(int encoding)350 public static int sampleSizeInBytes(int encoding) { 351 switch (encoding) { 352 case AudioFormat.ENCODING_PCM_16BIT: 353 return 2; 354 355 case AudioFormat.ENCODING_PCM_FLOAT: 356 return 4; 357 358 default: 359 return 0; 360 } 361 } 362 363 // 364 // State 365 // 366 /** 367 * @return See StreamState constants 368 */ getStreamState()369 public abstract int getStreamState(); 370 371 /** 372 * @return the ACTUAL number of channels in this stream 373 * (as opposed to the number requested). 374 * -1 if there is no valid stream. 375 */ getChannelCount()376 public abstract int getChannelCount(); 377 378 /** 379 * Note: The stream must be created before calling this method. 380 * @return true if the underlying stream is an MMAP stream, false otherwise. 381 */ isMMap()382 public abstract boolean isMMap(); 383 384 /** 385 * @return The last error callback result (these must match Oboe). See Oboe constants 386 */ getLastErrorCallbackResult()387 public abstract int getLastErrorCallbackResult(); 388 389 // 390 // Thread stuff 391 // 392 /** 393 * Joins the record thread to ensure that the stream is stopped. 394 */ waitForStreamThreadToExit()395 protected void waitForStreamThreadToExit() { 396 try { 397 if (mStreamThread != null) { 398 mStreamThread.join(); 399 mStreamThread = null; 400 } 401 } catch (InterruptedException e) { 402 e.printStackTrace(); 403 } 404 } 405 406 // 407 // Utility 408 // 409 /** 410 * @param chanCount The number of channels for which to generate an index mask. 411 * @return A channel index mask corresponding to the supplied channel count. 412 * 413 * note: The generated index mask has active channels from 0 to chanCount - 1 414 */ channelCountToIndexMask(int chanCount)415 public static int channelCountToIndexMask(int chanCount) { 416 return (1 << chanCount) - 1; 417 } 418 419 private static int[] sOutMasks = 420 { -1, 421 AudioFormat.CHANNEL_OUT_MONO, 422 AudioFormat.CHANNEL_OUT_STEREO, 423 AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER, 424 AudioFormat.CHANNEL_OUT_QUAD 425 }; 426 427 /** 428 * 429 * @param chanCount The number of channels for which to generate a postional mask. 430 * @return the corresponding channel position mask 431 * note: This mapping is not well defined, but may be needed to get a fast path in the Java API 432 */ channelCountToOutPositionMask(int chanCount)433 public static int channelCountToOutPositionMask(int chanCount) { 434 return chanCount <= 4 ? sOutMasks[chanCount] : AudioFormat.CHANNEL_OUT_STEREO; 435 } 436 } 437