diff options
-rw-r--r-- | android/src/org/rockbox/RockboxPCM.java | 99 |
1 files changed, 47 insertions, 52 deletions
diff --git a/android/src/org/rockbox/RockboxPCM.java b/android/src/org/rockbox/RockboxPCM.java index 47bc42f976..48178fcc92 100644 --- a/android/src/org/rockbox/RockboxPCM.java +++ b/android/src/org/rockbox/RockboxPCM.java | |||
@@ -22,7 +22,6 @@ | |||
22 | package org.rockbox; | 22 | package org.rockbox; |
23 | 23 | ||
24 | import java.util.Arrays; | 24 | import java.util.Arrays; |
25 | |||
26 | import android.content.BroadcastReceiver; | 25 | import android.content.BroadcastReceiver; |
27 | import android.content.Context; | 26 | import android.content.Context; |
28 | import android.content.Intent; | 27 | import android.content.Intent; |
@@ -37,10 +36,6 @@ import android.util.Log; | |||
37 | 36 | ||
38 | public class RockboxPCM extends AudioTrack | 37 | public class RockboxPCM extends AudioTrack |
39 | { | 38 | { |
40 | private byte[] raw_data; | ||
41 | private PCMListener l; | ||
42 | private HandlerThread ht; | ||
43 | private Handler h = null; | ||
44 | private static final int streamtype = AudioManager.STREAM_MUSIC; | 39 | private static final int streamtype = AudioManager.STREAM_MUSIC; |
45 | private static final int samplerate = 44100; | 40 | private static final int samplerate = 44100; |
46 | /* should be CHANNEL_OUT_STEREO in 2.0 and above */ | 41 | /* should be CHANNEL_OUT_STEREO in 2.0 and above */ |
@@ -48,16 +43,20 @@ public class RockboxPCM extends AudioTrack | |||
48 | AudioFormat.CHANNEL_CONFIGURATION_STEREO; | 43 | AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
49 | private static final int encoding = | 44 | private static final int encoding = |
50 | AudioFormat.ENCODING_PCM_16BIT; | 45 | AudioFormat.ENCODING_PCM_16BIT; |
51 | /* 24k is plenty, but some devices may have a higher minimum */ | 46 | /* 32k is plenty, but some devices may have a higher minimum */ |
52 | private static final int buf_len = | 47 | private static final int buf_len = |
53 | Math.max(24<<10, getMinBufferSize(samplerate, channels, encoding)); | 48 | Math.max(32<<10, 4*getMinBufferSize(samplerate, channels, encoding)); |
54 | 49 | ||
55 | private AudioManager audiomanager; | 50 | private AudioManager audiomanager; |
51 | private RockboxService rbservice; | ||
52 | private byte[] raw_data; | ||
53 | |||
54 | private int refillmark; | ||
56 | private int maxstreamvolume; | 55 | private int maxstreamvolume; |
57 | private int setstreamvolume = -1; | 56 | private int setstreamvolume = -1; |
58 | private float minpcmvolume; | 57 | private float minpcmvolume; |
58 | private float curpcmvolume = 0; | ||
59 | private float pcmrange; | 59 | private float pcmrange; |
60 | private RockboxService rbservice; | ||
61 | 60 | ||
62 | private void LOG(CharSequence text) | 61 | private void LOG(CharSequence text) |
63 | { | 62 | { |
@@ -68,12 +67,11 @@ public class RockboxPCM extends AudioTrack | |||
68 | { | 67 | { |
69 | super(streamtype, samplerate, channels, encoding, | 68 | super(streamtype, samplerate, channels, encoding, |
70 | buf_len, AudioTrack.MODE_STREAM); | 69 | buf_len, AudioTrack.MODE_STREAM); |
71 | ht = new HandlerThread("audio thread", | 70 | HandlerThread ht = new HandlerThread("audio thread", |
72 | Process.THREAD_PRIORITY_URGENT_AUDIO); | 71 | Process.THREAD_PRIORITY_URGENT_AUDIO); |
73 | ht.start(); | 72 | ht.start(); |
74 | raw_data = new byte[buf_len]; /* in shorts */ | 73 | raw_data = new byte[buf_len]; /* in shorts */ |
75 | Arrays.fill(raw_data, (byte) 0); | 74 | Arrays.fill(raw_data, (byte) 0); |
76 | l = new PCMListener(buf_len); | ||
77 | 75 | ||
78 | /* find cleaner way to get context? */ | 76 | /* find cleaner way to get context? */ |
79 | rbservice = RockboxService.get_instance(); | 77 | rbservice = RockboxService.get_instance(); |
@@ -86,6 +84,13 @@ public class RockboxPCM extends AudioTrack | |||
86 | 84 | ||
87 | setupVolumeHandler(); | 85 | setupVolumeHandler(); |
88 | postVolume(audiomanager.getStreamVolume(streamtype)); | 86 | postVolume(audiomanager.getStreamVolume(streamtype)); |
87 | refillmark = buf_len / 4; /* higher values don't work on many devices */ | ||
88 | |||
89 | /* getLooper() returns null if thread isn't running */ | ||
90 | while(!ht.isAlive()) Thread.yield(); | ||
91 | setPlaybackPositionUpdateListener( | ||
92 | new PCMListener(buf_len / 2), new Handler(ht.getLooper())); | ||
93 | refillmark = bytes2frames(refillmark); | ||
89 | } | 94 | } |
90 | 95 | ||
91 | private native void postVolumeChangedEvent(int volume); | 96 | private native void postVolumeChangedEvent(int volume); |
@@ -133,18 +138,21 @@ public class RockboxPCM extends AudioTrack | |||
133 | new IntentFilter("android.media.VOLUME_CHANGED_ACTION")); | 138 | new IntentFilter("android.media.VOLUME_CHANGED_ACTION")); |
134 | } | 139 | } |
135 | 140 | ||
141 | @SuppressWarnings("unused") | ||
136 | private int bytes2frames(int bytes) | 142 | private int bytes2frames(int bytes) |
137 | { | 143 | { |
138 | /* 1 sample is 2 bytes, 2 samples are 1 frame */ | 144 | /* 1 sample is 2 bytes, 2 samples are 1 frame */ |
139 | return (bytes/4); | 145 | return (bytes/4); |
140 | } | 146 | } |
141 | 147 | ||
148 | @SuppressWarnings("unused") | ||
142 | private int frames2bytes(int frames) | 149 | private int frames2bytes(int frames) |
143 | { | 150 | { |
144 | /* 1 frame is 2 samples, 1 sample is 2 bytes */ | 151 | /* 1 frame is 2 samples, 1 sample is 2 bytes */ |
145 | return (frames*4); | 152 | return (frames*4); |
146 | } | 153 | } |
147 | 154 | ||
155 | @SuppressWarnings("unused") | ||
148 | private void play_pause(boolean pause) | 156 | private void play_pause(boolean pause) |
149 | { | 157 | { |
150 | RockboxService service = RockboxService.get_instance(); | 158 | RockboxService service = RockboxService.get_instance(); |
@@ -164,38 +172,45 @@ public class RockboxPCM extends AudioTrack | |||
164 | service.startForeground(); | 172 | service.startForeground(); |
165 | if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED) | 173 | if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED) |
166 | { | 174 | { |
167 | if (getState() == AudioTrack.STATE_INITIALIZED) | 175 | setNotificationMarkerPosition(refillmark); |
168 | { | 176 | /* need to fill with silence before starting playback */ |
169 | if (h == null) | 177 | write(raw_data, 0, raw_data.length); |
170 | h = new Handler(ht.getLooper()); | ||
171 | if (setNotificationMarkerPosition(bytes2frames(buf_len)/4) | ||
172 | != AudioTrack.SUCCESS) | ||
173 | LOG("setNotificationMarkerPosition Error"); | ||
174 | else | ||
175 | setPlaybackPositionUpdateListener(l, h); | ||
176 | } | ||
177 | /* need to fill with silence before starting playback */ | ||
178 | write(raw_data, frames2bytes(getPlaybackHeadPosition()), | ||
179 | raw_data.length); | ||
180 | } | 178 | } |
181 | play(); | 179 | play(); |
182 | } | 180 | } |
183 | } | 181 | } |
184 | 182 | ||
185 | @Override | 183 | @Override |
186 | public void stop() throws IllegalStateException | 184 | public synchronized void stop() throws IllegalStateException |
187 | { | 185 | { |
186 | /* flush pending data, but turn the volume off so it cannot be heard. | ||
187 | * This is so that we don't hear old data if music is resumed very | ||
188 | * quickly after (e.g. when seeking). | ||
189 | */ | ||
190 | float old_vol = curpcmvolume; | ||
188 | try { | 191 | try { |
192 | setStereoVolume(0, 0); | ||
193 | flush(); | ||
189 | super.stop(); | 194 | super.stop(); |
190 | } catch (IllegalStateException e) { | 195 | } catch (IllegalStateException e) { |
191 | throw new IllegalStateException(e); | 196 | throw new IllegalStateException(e); |
197 | } finally { | ||
198 | setStereoVolume(old_vol, old_vol); | ||
192 | } | 199 | } |
200 | |||
193 | Intent widgetUpdate = new Intent("org.rockbox.UpdateState"); | 201 | Intent widgetUpdate = new Intent("org.rockbox.UpdateState"); |
194 | widgetUpdate.putExtra("state", "stop"); | 202 | widgetUpdate.putExtra("state", "stop"); |
195 | RockboxService.get_instance().sendBroadcast(widgetUpdate); | 203 | RockboxService.get_instance().sendBroadcast(widgetUpdate); |
196 | RockboxService.get_instance().stopForeground(); | 204 | RockboxService.get_instance().stopForeground(); |
197 | } | 205 | } |
206 | |||
207 | public int setStereoVolume(float leftVolume, float rightVolume) | ||
208 | { | ||
209 | curpcmvolume = leftVolume; | ||
210 | return super.setStereoVolume(leftVolume, rightVolume); | ||
211 | } | ||
198 | 212 | ||
213 | @SuppressWarnings("unused") | ||
199 | private void set_volume(int volume) | 214 | private void set_volume(int volume) |
200 | { | 215 | { |
201 | LOG("java:set_volume("+volume+")"); | 216 | LOG("java:set_volume("+volume+")"); |
@@ -228,15 +243,10 @@ public class RockboxPCM extends AudioTrack | |||
228 | 243 | ||
229 | private class PCMListener implements OnPlaybackPositionUpdateListener | 244 | private class PCMListener implements OnPlaybackPositionUpdateListener |
230 | { | 245 | { |
231 | private int max_len; | ||
232 | private int refill_mark; | ||
233 | private byte[] buf; | 246 | private byte[] buf; |
234 | public PCMListener(int len) | 247 | public PCMListener(int refill_bufsize) |
235 | { | 248 | { |
236 | max_len = len; | 249 | buf = new byte[refill_bufsize]; |
237 | /* refill to 100% when reached the 25% */ | ||
238 | buf = new byte[max_len*3/4]; | ||
239 | refill_mark = max_len - buf.length; | ||
240 | } | 250 | } |
241 | 251 | ||
242 | public void onMarkerReached(AudioTrack track) | 252 | public void onMarkerReached(AudioTrack track) |
@@ -248,32 +258,17 @@ public class RockboxPCM extends AudioTrack | |||
248 | result = track.write(buf, 0, buf.length); | 258 | result = track.write(buf, 0, buf.length); |
249 | if (result >= 0) | 259 | if (result >= 0) |
250 | { | 260 | { |
251 | switch(track.getPlayState()) | 261 | switch(getPlayState()) |
252 | { | 262 | { |
253 | case AudioTrack.PLAYSTATE_PLAYING: | 263 | case PLAYSTATE_PLAYING: |
254 | case AudioTrack.PLAYSTATE_PAUSED: | 264 | case PLAYSTATE_PAUSED: |
255 | /* refill at 25% no matter of how many | 265 | setNotificationMarkerPosition(pcm.refillmark); |
256 | * bytes we've written */ | ||
257 | if (setNotificationMarkerPosition( | ||
258 | bytes2frames(refill_mark)) | ||
259 | != AudioTrack.SUCCESS) | ||
260 | { | ||
261 | LOG("Error in onMarkerReached: " + | ||
262 | "Could not set notification marker"); | ||
263 | } | ||
264 | else /* recharge */ | ||
265 | setPlaybackPositionUpdateListener(this, h); | ||
266 | break; | 266 | break; |
267 | case AudioTrack.PLAYSTATE_STOPPED: | 267 | case PLAYSTATE_STOPPED: |
268 | LOG("State STOPPED"); | 268 | LOG("Stopped"); |
269 | break; | 269 | break; |
270 | } | 270 | } |
271 | } | 271 | } |
272 | else | ||
273 | { | ||
274 | LOG("Error in onMarkerReached (result="+result+")"); | ||
275 | stop(); | ||
276 | } | ||
277 | } | 272 | } |
278 | 273 | ||
279 | public void onPeriodicNotification(AudioTrack track) | 274 | public void onPeriodicNotification(AudioTrack track) |