package org.libsdl.app; import android.media.*; import android.util.Log; public class SDLAudioManager { protected static final String TAG = "SDLAudio"; protected static AudioTrack mAudioTrack; protected static AudioRecord mAudioRecord; public static void initialize() { mAudioTrack = null; mAudioRecord = null; } // Audio /** * This method is called by SDL using JNI. */ public static int audioOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); // Let the user pick a larger buffer if they really want -- but ye // gods they probably shouldn't, the minimums are horrifyingly high // latency already desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); if (mAudioTrack == null) { mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { Log.e(TAG, "Failed during initialization of Audio Track"); mAudioTrack = null; return -1; } mAudioTrack.play(); } Log.v(TAG, "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); return 0; } /** * This method is called by SDL using JNI. */ public static void audioWriteShortBuffer(short[] buffer) { if (mAudioTrack == null) { Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); return; } for (int i = 0; i < buffer.length; ) { int result = mAudioTrack.write(buffer, i, buffer.length - i); if (result > 0) { i += result; } else if (result == 0) { try { Thread.sleep(1); } catch(InterruptedException e) { // Nom nom } } else { Log.w(TAG, "SDL audio: error return from write(short)"); return; } } } /** * This method is called by SDL using JNI. */ public static void audioWriteByteBuffer(byte[] buffer) { if (mAudioTrack == null) { Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); return; } for (int i = 0; i < buffer.length; ) { int result = mAudioTrack.write(buffer, i, buffer.length - i); if (result > 0) { i += result; } else if (result == 0) { try { Thread.sleep(1); } catch(InterruptedException e) { // Nom nom } } else { Log.w(TAG, "SDL audio: error return from write(byte)"); return; } } } /** * This method is called by SDL using JNI. */ public static int captureOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); Log.v(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); // Let the user pick a larger buffer if they really want -- but ye // gods they probably shouldn't, the minimums are horrifyingly high // latency already desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); if (mAudioRecord == null) { mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize); // see notes about AudioTrack state in audioOpen(), above. Probably also applies here. if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) { Log.e(TAG, "Failed during initialization of AudioRecord"); mAudioRecord.release(); mAudioRecord = null; return -1; } mAudioRecord.startRecording(); } Log.v(TAG, "SDL capture: got " + ((mAudioRecord.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioRecord.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); return 0; } /** This method is called by SDL using JNI. */ public static int captureReadShortBuffer(short[] buffer, boolean blocking) { // !!! FIXME: this is available in API Level 23. Until then, we always block. :( //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); return mAudioRecord.read(buffer, 0, buffer.length); } /** This method is called by SDL using JNI. */ public static int captureReadByteBuffer(byte[] buffer, boolean blocking) { // !!! FIXME: this is available in API Level 23. Until then, we always block. :( //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); return mAudioRecord.read(buffer, 0, buffer.length); } /** This method is called by SDL using JNI. */ public static void audioClose() { if (mAudioTrack != null) { mAudioTrack.stop(); mAudioTrack.release(); mAudioTrack = null; } } /** This method is called by SDL using JNI. */ public static void captureClose() { if (mAudioRecord != null) { mAudioRecord.stop(); mAudioRecord.release(); mAudioRecord = null; } } public static native int nativeSetupJNI(); }