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 in this stream. 83 */ 84 protected int mChannelCount; 85 86 /** 87 * The sample rate for this stream 88 */ 89 protected int mSampleRate; 90 91 /** 92 * The number of frames exchanged between the stream and the AudioSink/AudioSource. 93 * It is not (necessarily) the number of frames exchange with the OS player/recorder. 94 */ 95 protected int mNumExchangeFrames; 96 97 /** 98 * The performance mode for this stream. 99 * See Performance Mode Constants in Builder class. 100 */ 101 protected int mPerformanceMode; 102 103 /** 104 * The sharing mode for this stream. See Sharing Mode Constants in Builder class. 105 */ 106 protected int mSharingMode; 107 108 //TODO - Add methods for changing the routing of an instantiated stream. 109 110 // the thread on which the underlying Android AudioTrack/AudioRecord will run 111 protected Thread mStreamThread = null; 112 113 // 114 // Initialization 115 // 116 117 /** 118 * Forces the load of the MegaAudio (native) library 119 */ loadMegaAudioLibrary()120 public static void loadMegaAudioLibrary() { 121 // NOP. This will force the static load 122 } 123 124 /** 125 * Performs initialization. MUST be called before any Streams are created. 126 * @param context 127 */ setup(Context context)128 public static void setup(Context context) { 129 calcNumBurstFrames(context); 130 calcSystemSampleRate(context); 131 } 132 133 // 134 // Attributes 135 // 136 137 /** 138 * @return The number of channels associated with this stream. 139 */ getChannelCount()140 public int getChannelCount() { return mChannelCount; } 141 142 /** 143 * @return The sample rate for this stream. 144 */ getSampleRate()145 public int getSampleRate() { return mSampleRate; } 146 147 /** 148 * Gets the system-specified burst-size in frames. This should be called by the 149 * app in initialization before calling getSystemBurstFrames() (below). 150 * @return the system-specified burst size in frames. 151 */ calcNumBurstFrames(Context context)152 public static int calcNumBurstFrames(Context context) { 153 AudioManager audioManager = context.getSystemService(AudioManager.class); 154 String text = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 155 sSystemBurstFrames = Integer.parseInt(text); 156 if (LOG) { 157 Log.d(TAG, "sSystemBurstFrames:" + sSystemBurstFrames); 158 } 159 return sSystemBurstFrames; 160 } 161 162 /** 163 * @return the system-specified burst size in frames. 164 */ getSystemBurstFrames()165 public static int getSystemBurstFrames() { 166 return sSystemBurstFrames; 167 } 168 169 /** 170 * @param api Specifies which API BuilderBase.TYPE_NONE, BuilderBase.TYPE_JAVA 171 * or BuilderBase.TYPE_OBOE 172 * @return The optimal capacity for a stream buffer of the specified type. 173 */ getNumBurstFrames(int api)174 public static int getNumBurstFrames(int api) { 175 return sSystemBurstFrames; 176 } 177 178 /** 179 * 180 */ getNumExchangeFrames()181 public int getNumExchangeFrames() { 182 return mNumExchangeFrames; 183 } 184 185 /** 186 * Gets the system-speficied preferred sample rate for audio. This should be called by the 187 * * app in initialization before calling getSystemSampleRate() (below). 188 * @return the system preferred sample rate 189 */ calcSystemSampleRate(Context context)190 public static int calcSystemSampleRate(Context context) { 191 AudioManager audioManager = context.getSystemService(AudioManager.class); 192 String text = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 193 return sSystemSampleRate = Integer.parseInt(text); 194 } 195 196 /** 197 * @return the system preferred sample rate 198 */ getSystemSampleRate()199 public static int getSystemSampleRate() { 200 return sSystemSampleRate; 201 } 202 203 // Routing getRoutedDeviceId()204 public abstract int getRoutedDeviceId(); 205 206 // 207 // Sample Format Utils 208 // 209 /** 210 * @param encoding An Android ENCODING_ constant for audio data. 211 * @return The size in BYTES of samples encoded as specified. 212 */ sampleSizeInBytes(int encoding)213 public static int sampleSizeInBytes(int encoding) { 214 switch (encoding) { 215 case AudioFormat.ENCODING_PCM_16BIT: 216 return 2; 217 218 case AudioFormat.ENCODING_PCM_FLOAT: 219 return 4; 220 221 default: 222 return 0; 223 } 224 } 225 226 // 227 // State 228 // 229 /** 230 * Releases resources used by the stream. 231 * @return 232 */ teardownStream()233 public abstract int teardownStream(); 234 235 /** 236 * Starts playback on an open stream player. (@see open() method above). 237 * @return ERROR_NONE if successful, otherwise an error code 238 */ startStream()239 public abstract int startStream(); 240 241 /** 242 * Stops playback. 243 * May not stop the stream immediately. i.e. does not stop until the next audio callback 244 * from the underlying system. 245 * @return ERROR_NONE if successful, otherwise an error code 246 */ stopStream()247 public abstract int stopStream(); 248 249 /** 250 * @return See StreamState constants 251 */ getStreamState()252 public abstract int getStreamState(); 253 254 /** 255 * @return The last error callback result (these must match Oboe). See Oboe constants 256 */ getLastErrorCallbackResult()257 public abstract int getLastErrorCallbackResult(); 258 259 // 260 // Thread stuff 261 // 262 /** 263 * Joins the record thread to ensure that the stream is stopped. 264 */ waitForStreamThreadToExit()265 protected void waitForStreamThreadToExit() { 266 try { 267 if (mStreamThread != null) { 268 mStreamThread.join(); 269 mStreamThread = null; 270 } 271 } catch (InterruptedException e) { 272 e.printStackTrace(); 273 } 274 } 275 276 // 277 // Utility 278 // 279 /** 280 * @param chanCount The number of channels for which to generate an index mask. 281 * @return A channel index mask corresponding to the supplied channel count. 282 * 283 * note: The generated index mask has active channels from 0 to chanCount - 1 284 */ channelCountToIndexMask(int chanCount)285 public static int channelCountToIndexMask(int chanCount) { 286 return (1 << chanCount) - 1; 287 } 288 289 private static int[] sOutMasks = 290 { -1, 291 AudioFormat.CHANNEL_OUT_MONO, 292 AudioFormat.CHANNEL_OUT_STEREO, 293 AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER, 294 AudioFormat.CHANNEL_OUT_QUAD 295 }; 296 297 /** 298 * 299 * @param chanCount The number of channels for which to generate a postional mask. 300 * @return the corresponding channel position mask 301 * note: This mapping is not well defined, but may be needed to get a fast path in the Java API 302 */ channelCountToOutPositionMask(int chanCount)303 public static int channelCountToOutPositionMask(int chanCount) { 304 return chanCount <= 4 ? sOutMasks[chanCount] : AudioFormat.CHANNEL_OUT_STEREO; 305 } 306 } 307