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