/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.hyphonate.megaaudio.common; import android.content.Context; import android.media.AudioFormat; import android.media.AudioManager; import android.util.Log; // For initialization import org.hyphonate.megaaudio.player.JavaSourceProxy; /** * Common base class for all audio streams. */ public abstract class StreamBase { @SuppressWarnings("unused") private static final String TAG = StreamBase.class.getSimpleName(); @SuppressWarnings("unused") private static final boolean LOG = true; static { if (LOG) { Log.d(TAG, "Loading MegaAudio Library..."); } try { System.loadLibrary("megaaudio_jni"); JavaSourceProxy.initN(); } catch (UnsatisfiedLinkError e) { Log.e(TAG, "Error loading MegaAudio JNI library"); Log.e(TAG, "e: " + e); e.printStackTrace(); } /* TODO: gracefully fail/notify if the library can't be loaded */ } // // Error Codes // These values must be kept in sync with the equivalent symbols in // megaaudio/common/Streambase.h // public static final int OK = 0; public static final int ERROR_UNKNOWN = -1; public static final int ERROR_UNSUPPORTED = -2; public static final int ERROR_INVALID_STATE = -3; public static final int ERROR_DISCONNECTED = -899; // must match Oboe public static final int ERROR_INVALIDSTATE = -895; // // System Attributes // /** * The size of the system "burst" buffer in frames. * Note: Apps need to call calcNumBurstFrames(Context) to initialize this * with the actual value for the system. 512 is an arbitrary, but safe value. */ private static int sSystemBurstFrames = 512; /** * The Preferred system sample rate. */ private static int sSystemSampleRate = 48000; // // Stream attributes // /** * The number of channels requested for this stream. */ protected int mChannelCount; protected int mChannelMask; /** * The sample rate for this stream */ protected int mSampleRate; /** * The number of frames exchanged between the stream and the AudioSink/AudioSource. * It is not (necessarily) the number of frames exchange with the OS player/recorder. */ protected int mNumExchangeFrames; /** * The performance mode for this stream. * See Performance Mode Constants in Builder class. */ protected int mPerformanceMode; /** * The sharing mode for this stream. See Sharing Mode Constants in Builder class. */ protected int mSharingMode; /** * @return the sharing mode for the (open) stream */ public abstract int getSharingMode(); //TODO - Add methods for changing the routing of an instantiated stream. // the thread on which the underlying Android AudioTrack/AudioRecord will run protected Thread mStreamThread = null; // // Initialization // /** * Forces the load of the MegaAudio (native) library */ public static void loadMegaAudioLibrary() { // NOP. This will force the static load } /** * Performs initialization. MUST be called before any Streams are created. * @param context */ public static void setup(Context context) { calcNumBurstFrames(context); calcSystemSampleRate(context); } // // Attributes // /** * @return The sample rate for this stream. */ public int getSampleRate() { return mSampleRate; } /** * Gets the system-specified burst-size in frames. This should be called by the * app in initialization before calling getSystemBurstFrames() (below). * @return the system-specified burst size in frames. */ public static int calcNumBurstFrames(Context context) { AudioManager audioManager = context.getSystemService(AudioManager.class); String text = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); sSystemBurstFrames = Integer.parseInt(text); if (LOG) { Log.d(TAG, "sSystemBurstFrames:" + sSystemBurstFrames); } return sSystemBurstFrames; } /** * @return the system-specified burst size in frames. */ public static int getSystemBurstFrames() { return sSystemBurstFrames; } /** * @param api Specifies which API BuilderBase.TYPE_NONE, BuilderBase.TYPE_JAVA * or BuilderBase.TYPE_OBOE * @return The optimal capacity for a stream buffer of the specified type. */ public static int getNumBurstFrames(int api) { return sSystemBurstFrames; } /** * */ public int getNumExchangeFrames() { return mNumExchangeFrames; } /** * Gets the system-speficied preferred sample rate for audio. This should be called by the * * app in initialization before calling getSystemSampleRate() (below). * @return the system preferred sample rate */ public static int calcSystemSampleRate(Context context) { AudioManager audioManager = context.getSystemService(AudioManager.class); String text = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); return sSystemSampleRate = Integer.parseInt(text); } /** * @return the system preferred sample rate */ public static int getSystemSampleRate() { return sSystemSampleRate; } // Routing public abstract int getRoutedDeviceId(); // // Sample Format Utils // /** * @param encoding An Android ENCODING_ constant for audio data. * @return The size in BYTES of samples encoded as specified. */ public static int sampleSizeInBytes(int encoding) { switch (encoding) { case AudioFormat.ENCODING_PCM_16BIT: return 2; case AudioFormat.ENCODING_PCM_FLOAT: return 4; default: return 0; } } // // State // /** * Releases resources used by the stream. * @return */ public abstract int teardownStream(); /** * Starts playback on an open stream player. (@see open() method above). * @return ERROR_NONE if successful, otherwise an error code */ public abstract int startStream(); /** * Stops playback. * May not stop the stream immediately. i.e. does not stop until the next audio callback * from the underlying system. * @return ERROR_NONE if successful, otherwise an error code */ public abstract int stopStream(); /** * @return See StreamState constants */ public abstract int getStreamState(); /** * @return the ACTUAL number of channels in this stream * (as opposed to the number requested). * -1 if there is no valid stream. */ public abstract int getChannelCount(); /** * Note: The stream must be created before calling this method. * @return true if the underlying stream is an MMAP stream, false otherwise. */ public abstract boolean isMMap(); /** * @return The last error callback result (these must match Oboe). See Oboe constants */ public abstract int getLastErrorCallbackResult(); // // Thread stuff // /** * Joins the record thread to ensure that the stream is stopped. */ protected void waitForStreamThreadToExit() { try { if (mStreamThread != null) { mStreamThread.join(); mStreamThread = null; } } catch (InterruptedException e) { e.printStackTrace(); } } // // Utility // /** * @param chanCount The number of channels for which to generate an index mask. * @return A channel index mask corresponding to the supplied channel count. * * note: The generated index mask has active channels from 0 to chanCount - 1 */ public static int channelCountToIndexMask(int chanCount) { return (1 << chanCount) - 1; } private static int[] sOutMasks = { -1, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER, AudioFormat.CHANNEL_OUT_QUAD }; /** * * @param chanCount The number of channels for which to generate a postional mask. * @return the corresponding channel position mask * note: This mapping is not well defined, but may be needed to get a fast path in the Java API */ public static int channelCountToOutPositionMask(int chanCount) { return chanCount <= 4 ? sOutMasks[chanCount] : AudioFormat.CHANNEL_OUT_STEREO; } }