changed build system for Android from Ant to Gradle
[rocksndiamonds.git] / build-projects / android / app / src / main / java / org / libsdl / app / SDLAudioManager.java
1 package org.libsdl.app;
2
3 import android.media.*;
4 import android.os.Build;
5 import android.util.Log;
6
7 public class SDLAudioManager
8 {
9     protected static final String TAG = "SDLAudio";
10
11     protected static AudioTrack mAudioTrack;
12     protected static AudioRecord mAudioRecord;
13
14     public static void initialize() {
15         mAudioTrack = null;
16         mAudioRecord = null;
17     }
18
19     // Audio
20
21     protected static String getAudioFormatString(int audioFormat) {
22         switch (audioFormat) {
23         case AudioFormat.ENCODING_PCM_8BIT:
24             return "8-bit";
25         case AudioFormat.ENCODING_PCM_16BIT:
26             return "16-bit";
27         case AudioFormat.ENCODING_PCM_FLOAT:
28             return "float";
29         default:
30             return Integer.toString(audioFormat);
31         }
32     }
33
34     protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
35         int channelConfig;
36         int sampleSize;
37         int frameSize;
38
39         Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
40
41         /* On older devices let's use known good settings */
42         if (Build.VERSION.SDK_INT < 21) {
43             if (desiredChannels > 2) {
44                 desiredChannels = 2;
45             }
46             if (sampleRate < 8000) {
47                 sampleRate = 8000;
48             } else if (sampleRate > 48000) {
49                 sampleRate = 48000;
50             }
51         }
52
53         if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
54             int minSDKVersion = (isCapture ? 23 : 21);
55             if (Build.VERSION.SDK_INT < minSDKVersion) {
56                 audioFormat = AudioFormat.ENCODING_PCM_16BIT;
57             }
58         }
59         switch (audioFormat)
60         {
61         case AudioFormat.ENCODING_PCM_8BIT:
62             sampleSize = 1;
63             break;
64         case AudioFormat.ENCODING_PCM_16BIT:
65             sampleSize = 2;
66             break;
67         case AudioFormat.ENCODING_PCM_FLOAT:
68             sampleSize = 4;
69             break;
70         default:
71             Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
72             audioFormat = AudioFormat.ENCODING_PCM_16BIT;
73             sampleSize = 2;
74             break;
75         }
76
77         if (isCapture) {
78             switch (desiredChannels) {
79             case 1:
80                 channelConfig = AudioFormat.CHANNEL_IN_MONO;
81                 break;
82             case 2:
83                 channelConfig = AudioFormat.CHANNEL_IN_STEREO;
84                 break;
85             default:
86                 Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
87                 desiredChannels = 2;
88                 channelConfig = AudioFormat.CHANNEL_IN_STEREO;
89                 break;
90             }
91         } else {
92             switch (desiredChannels) {
93             case 1:
94                 channelConfig = AudioFormat.CHANNEL_OUT_MONO;
95                 break;
96             case 2:
97                 channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
98                 break;
99             case 3:
100                 channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
101                 break;
102             case 4:
103                 channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
104                 break;
105             case 5:
106                 channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
107                 break;
108             case 6:
109                 channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
110                 break;
111             case 7:
112                 channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
113                 break;
114             case 8:
115                 if (Build.VERSION.SDK_INT >= 23) {
116                     channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
117                 } else {
118                     Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
119                     desiredChannels = 6;
120                     channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
121                 }
122                 break;
123             default:
124                 Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
125                 desiredChannels = 2;
126                 channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
127                 break;
128             }
129
130 /*
131             Log.v(TAG, "Speaker configuration (and order of channels):");
132
133             if ((channelConfig & 0x00000004) != 0) {
134                 Log.v(TAG, "   CHANNEL_OUT_FRONT_LEFT");
135             }
136             if ((channelConfig & 0x00000008) != 0) {
137                 Log.v(TAG, "   CHANNEL_OUT_FRONT_RIGHT");
138             }
139             if ((channelConfig & 0x00000010) != 0) {
140                 Log.v(TAG, "   CHANNEL_OUT_FRONT_CENTER");
141             }
142             if ((channelConfig & 0x00000020) != 0) {
143                 Log.v(TAG, "   CHANNEL_OUT_LOW_FREQUENCY");
144             }
145             if ((channelConfig & 0x00000040) != 0) {
146                 Log.v(TAG, "   CHANNEL_OUT_BACK_LEFT");
147             }
148             if ((channelConfig & 0x00000080) != 0) {
149                 Log.v(TAG, "   CHANNEL_OUT_BACK_RIGHT");
150             }
151             if ((channelConfig & 0x00000100) != 0) {
152                 Log.v(TAG, "   CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
153             }
154             if ((channelConfig & 0x00000200) != 0) {
155                 Log.v(TAG, "   CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
156             }
157             if ((channelConfig & 0x00000400) != 0) {
158                 Log.v(TAG, "   CHANNEL_OUT_BACK_CENTER");
159             }
160             if ((channelConfig & 0x00000800) != 0) {
161                 Log.v(TAG, "   CHANNEL_OUT_SIDE_LEFT");
162             }
163             if ((channelConfig & 0x00001000) != 0) {
164                 Log.v(TAG, "   CHANNEL_OUT_SIDE_RIGHT");
165             }
166 */
167         }
168         frameSize = (sampleSize * desiredChannels);
169
170         // Let the user pick a larger buffer if they really want -- but ye
171         // gods they probably shouldn't, the minimums are horrifyingly high
172         // latency already
173         int minBufferSize;
174         if (isCapture) {
175             minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
176         } else {
177             minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
178         }
179         desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
180
181         int[] results = new int[4];
182
183         if (isCapture) {
184             if (mAudioRecord == null) {
185                 mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
186                         channelConfig, audioFormat, desiredFrames * frameSize);
187
188                 // see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
189                 if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
190                     Log.e(TAG, "Failed during initialization of AudioRecord");
191                     mAudioRecord.release();
192                     mAudioRecord = null;
193                     return null;
194                 }
195
196                 mAudioRecord.startRecording();
197             }
198
199             results[0] = mAudioRecord.getSampleRate();
200             results[1] = mAudioRecord.getAudioFormat();
201             results[2] = mAudioRecord.getChannelCount();
202             results[3] = desiredFrames;
203
204         } else {
205             if (mAudioTrack == null) {
206                 mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
207
208                 // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
209                 // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
210                 // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
211                 if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
212                     /* Try again, with safer values */
213
214                     Log.e(TAG, "Failed during initialization of Audio Track");
215                     mAudioTrack.release();
216                     mAudioTrack = null;
217                     return null;
218                 }
219
220                 mAudioTrack.play();
221             }
222
223             results[0] = mAudioTrack.getSampleRate();
224             results[1] = mAudioTrack.getAudioFormat();
225             results[2] = mAudioTrack.getChannelCount();
226             results[3] = desiredFrames;
227         }
228
229         Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
230
231         return results;
232     }
233
234     /**
235      * This method is called by SDL using JNI.
236      */
237     public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
238         return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames);
239     }
240
241     /**
242      * This method is called by SDL using JNI.
243      */
244     public static void audioWriteFloatBuffer(float[] buffer) {
245         if (mAudioTrack == null) {
246             Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
247             return;
248         }
249
250         for (int i = 0; i < buffer.length;) {
251             int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
252             if (result > 0) {
253                 i += result;
254             } else if (result == 0) {
255                 try {
256                     Thread.sleep(1);
257                 } catch(InterruptedException e) {
258                     // Nom nom
259                 }
260             } else {
261                 Log.w(TAG, "SDL audio: error return from write(float)");
262                 return;
263             }
264         }
265     }
266
267     /**
268      * This method is called by SDL using JNI.
269      */
270     public static void audioWriteShortBuffer(short[] buffer) {
271         if (mAudioTrack == null) {
272             Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
273             return;
274         }
275
276         for (int i = 0; i < buffer.length;) {
277             int result = mAudioTrack.write(buffer, i, buffer.length - i);
278             if (result > 0) {
279                 i += result;
280             } else if (result == 0) {
281                 try {
282                     Thread.sleep(1);
283                 } catch(InterruptedException e) {
284                     // Nom nom
285                 }
286             } else {
287                 Log.w(TAG, "SDL audio: error return from write(short)");
288                 return;
289             }
290         }
291     }
292
293     /**
294      * This method is called by SDL using JNI.
295      */
296     public static void audioWriteByteBuffer(byte[] buffer) {
297         if (mAudioTrack == null) {
298             Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
299             return;
300         }
301
302         for (int i = 0; i < buffer.length; ) {
303             int result = mAudioTrack.write(buffer, i, buffer.length - i);
304             if (result > 0) {
305                 i += result;
306             } else if (result == 0) {
307                 try {
308                     Thread.sleep(1);
309                 } catch(InterruptedException e) {
310                     // Nom nom
311                 }
312             } else {
313                 Log.w(TAG, "SDL audio: error return from write(byte)");
314                 return;
315             }
316         }
317     }
318
319     /**
320      * This method is called by SDL using JNI.
321      */
322     public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
323         return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames);
324     }
325
326     /** This method is called by SDL using JNI. */
327     public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
328         return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
329     }
330
331     /** This method is called by SDL using JNI. */
332     public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
333         if (Build.VERSION.SDK_INT < 23) {
334             return mAudioRecord.read(buffer, 0, buffer.length);
335         } else {
336             return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
337         }
338     }
339
340     /** This method is called by SDL using JNI. */
341     public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
342         if (Build.VERSION.SDK_INT < 23) {
343             return mAudioRecord.read(buffer, 0, buffer.length);
344         } else {
345             return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
346         }
347     }
348
349     /** This method is called by SDL using JNI. */
350     public static void audioClose() {
351         if (mAudioTrack != null) {
352             mAudioTrack.stop();
353             mAudioTrack.release();
354             mAudioTrack = null;
355         }
356     }
357
358     /** This method is called by SDL using JNI. */
359     public static void captureClose() {
360         if (mAudioRecord != null) {
361             mAudioRecord.stop();
362             mAudioRecord.release();
363             mAudioRecord = null;
364         }
365     }
366
367     /** This method is called by SDL using JNI. */
368     public static void audioSetThreadPriority(boolean iscapture, int device_id) {
369         try {
370
371             /* Set thread name */
372             if (iscapture) {
373                 Thread.currentThread().setName("SDLAudioC" + device_id);
374             } else {
375                 Thread.currentThread().setName("SDLAudioP" + device_id);
376             }
377
378             /* Set thread priority */
379             android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
380
381         } catch (Exception e) {
382             Log.v(TAG, "modify thread properties failed " + e.toString());
383         }
384     }
385
386     public static native int nativeSetupJNI();
387 }