summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Martitz <kugel@rockbox.org>2013-03-16 22:35:54 +0100
committerThomas Martitz <kugel@rockbox.org>2013-04-01 11:26:12 +0200
commit9f242e7be4f301e965d0bf35908a9bcaacdfdcae (patch)
tree55952fcaa07f6cdb0657f255c6cbf28cbd2c2c95
parent9add11d79a5e1516908a4935a3e538880ff38378 (diff)
downloadrockbox-9f242e7be4f301e965d0bf35908a9bcaacdfdcae.tar.gz
rockbox-9f242e7be4f301e965d0bf35908a9bcaacdfdcae.zip
android: Rewrite PCM playback without OnPlaybackPositionUpdateListener.
The old way actually mis-used the API (I misunderstood the docs) because it specified the marker position as a "low buffer watermark" but instead of a future playback head position. The replacement is a simple thread that writes the data regardless of the filling level of the buffer (write() will just block) and polls the playback state periodically. Change-Id: If29237cee4ce78dc42f5a8320878bab0cafe78f7 Reviewed-on: http://gerrit.rockbox.org/422 Tested-by: Dominik Riebeling <Dominik.Riebeling@gmail.com> Reviewed-by: Thomas Martitz <kugel@rockbox.org>
-rw-r--r--android/src/org/rockbox/RockboxPCM.java158
-rw-r--r--firmware/target/hosted/android/pcm-android.c20
2 files changed, 115 insertions, 63 deletions
diff --git a/android/src/org/rockbox/RockboxPCM.java b/android/src/org/rockbox/RockboxPCM.java
index 542860a38e..10caa772b6 100644
--- a/android/src/org/rockbox/RockboxPCM.java
+++ b/android/src/org/rockbox/RockboxPCM.java
@@ -43,10 +43,6 @@ public class RockboxPCM extends AudioTrack
43 AudioFormat.CHANNEL_OUT_STEREO; 43 AudioFormat.CHANNEL_OUT_STEREO;
44 private static final int encoding = 44 private static final int encoding =
45 AudioFormat.ENCODING_PCM_16BIT; 45 AudioFormat.ENCODING_PCM_16BIT;
46 /* 32k is plenty, but some devices may have a higher minimum */
47 private static final int buf_len =
48 Math.max(32<<10, 4*getMinBufferSize(samplerate, channels, encoding));
49
50 private AudioManager audiomanager; 46 private AudioManager audiomanager;
51 private RockboxService rbservice; 47 private RockboxService rbservice;
52 private byte[] raw_data; 48 private byte[] raw_data;
@@ -58,14 +54,20 @@ public class RockboxPCM extends AudioTrack
58 private float curpcmvolume = 0; 54 private float curpcmvolume = 0;
59 private float pcmrange; 55 private float pcmrange;
60 56
57 /* 8k is plenty, but some devices may have a higher minimum.
58 * 8k represents 125ms of audio */
59 private static final int chunkSize =
60 Math.max(8<<10, getMinBufferSize(samplerate, channels, encoding));
61 Streamer streamer;
62
61 public RockboxPCM() 63 public RockboxPCM()
62 { 64 {
63 super(streamtype, samplerate, channels, encoding, 65 super(streamtype, samplerate, channels, encoding,
64 buf_len, AudioTrack.MODE_STREAM); 66 chunkSize, AudioTrack.MODE_STREAM);
65 HandlerThread ht = new HandlerThread("audio thread", 67
66 Process.THREAD_PRIORITY_URGENT_AUDIO); 68 streamer = new Streamer(chunkSize);
67 ht.start(); 69 streamer.start();
68 raw_data = new byte[buf_len]; /* in shorts */ 70 raw_data = new byte[chunkSize]; /* in shorts */
69 Arrays.fill(raw_data, (byte) 0); 71 Arrays.fill(raw_data, (byte) 0);
70 72
71 /* find cleaner way to get context? */ 73 /* find cleaner way to get context? */
@@ -79,14 +81,80 @@ public class RockboxPCM extends AudioTrack
79 81
80 setupVolumeHandler(); 82 setupVolumeHandler();
81 postVolume(audiomanager.getStreamVolume(streamtype)); 83 postVolume(audiomanager.getStreamVolume(streamtype));
82 refillmark = buf_len / 4; /* higher values don't work on many devices */ 84 }
83 85
84 /* getLooper() returns null if thread isn't running */ 86 /**
85 while(!ht.isAlive()) Thread.yield(); 87 * This class does the actual playback work. Its run() method
86 setPlaybackPositionUpdateListener( 88 * continuously writes data to the AudioTrack. This operation blocks
87 new PCMListener(buf_len / 2), new Handler(ht.getLooper())); 89 * and should therefore be run on its own thread.
88 refillmark = bytes2frames(refillmark); 90 */
89 } 91 private class Streamer extends Thread
92 {
93 byte[] buffer;
94 private boolean quit = false;
95
96 Streamer(int bufsize)
97 {
98 super("audio thread");
99 buffer = new byte[bufsize];
100 }
101
102 @Override
103 public void run()
104 {
105 /* THREAD_PRIORITY_URGENT_AUDIO can only be specified via
106 * setThreadPriority(), and not via thread.setPriority(). This is
107 * also how the android's HandlerThread class implements it */
108 Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
109 while (!quit)
110 {
111 switch(getPlayState())
112 {
113 case PLAYSTATE_PLAYING:
114 nativeWrite(buffer, buffer.length);
115 break;
116 case PLAYSTATE_PAUSED:
117 case PLAYSTATE_STOPPED:
118 {
119 synchronized (this)
120 {
121 try
122 {
123 wait();
124 }
125 catch (InterruptedException e) { e.printStackTrace(); }
126 break;
127 }
128 }
129 }
130 }
131 }
132
133 synchronized void quit()
134 {
135 quit = true;
136 notify();
137 }
138
139 synchronized void kick()
140 {
141 notify();
142 }
143
144 void quitAndJoin()
145 {
146 while(true)
147 {
148 try
149 {
150 quit();
151 join();
152 return;
153 }
154 catch (InterruptedException e) { }
155 }
156 }
157 }
90 158
91 private native void postVolumeChangedEvent(int volume); 159 private native void postVolumeChangedEvent(int volume);
92 160
@@ -164,14 +232,22 @@ public class RockboxPCM extends AudioTrack
164 service.startForeground(); 232 service.startForeground();
165 if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED) 233 if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED)
166 { 234 {
167 setNotificationMarkerPosition(refillmark);
168 /* need to fill with silence before starting playback */ 235 /* need to fill with silence before starting playback */
169 write(raw_data, 0, raw_data.length); 236 write(raw_data, 0, raw_data.length);
170 } 237 }
171 play(); 238 play();
172 } 239 }
173 } 240 }
174 241
242 @Override
243 public void play() throws IllegalStateException
244 {
245 super.play();
246 /* when stopped or paused the streamer is in a wait() state. need
247 * it to wake it up */
248 streamer.kick();
249 }
250
175 @Override 251 @Override
176 public synchronized void stop() throws IllegalStateException 252 public synchronized void stop() throws IllegalStateException
177 { 253 {
@@ -195,7 +271,15 @@ public class RockboxPCM extends AudioTrack
195 RockboxService.getInstance().sendBroadcast(widgetUpdate); 271 RockboxService.getInstance().sendBroadcast(widgetUpdate);
196 RockboxService.getInstance().stopForeground(); 272 RockboxService.getInstance().stopForeground();
197 } 273 }
198 274
275 @Override
276 public void release()
277 {
278 super.release();
279 /* stop streamer if this AudioTrack is destroyed by whomever */
280 streamer.quitAndJoin();
281 }
282
199 public int setStereoVolume(float leftVolume, float rightVolume) 283 public int setStereoVolume(float leftVolume, float rightVolume)
200 { 284 {
201 curpcmvolume = leftVolume; 285 curpcmvolume = leftVolume;
@@ -231,40 +315,4 @@ public class RockboxPCM extends AudioTrack
231 } 315 }
232 316
233 public native int nativeWrite(byte[] temp, int len); 317 public native int nativeWrite(byte[] temp, int len);
234
235 private class PCMListener implements OnPlaybackPositionUpdateListener
236 {
237 byte[] pcm_data;
238 public PCMListener(int _refill_bufsize)
239 {
240 pcm_data = new byte[_refill_bufsize];
241 }
242
243 public void onMarkerReached(AudioTrack track)
244 {
245 /* push new data to the hardware */
246 RockboxPCM pcm = (RockboxPCM)track;
247 int result = -1;
248 result = pcm.nativeWrite(pcm_data, pcm_data.length);
249 if (result >= 0)
250 {
251 switch(getPlayState())
252 {
253 case PLAYSTATE_PLAYING:
254 case PLAYSTATE_PAUSED:
255 setNotificationMarkerPosition(pcm.refillmark);
256 break;
257 case PLAYSTATE_STOPPED:
258 Logger.d("Stopped");
259 break;
260 }
261 }
262 else /* stop on error */
263 stop();
264 }
265
266 public void onPeriodicNotification(AudioTrack track)
267 {
268 }
269 }
270} 318}
diff --git a/firmware/target/hosted/android/pcm-android.c b/firmware/target/hosted/android/pcm-android.c
index 0428e5f541..0608e971a7 100644
--- a/firmware/target/hosted/android/pcm-android.c
+++ b/firmware/target/hosted/android/pcm-android.c
@@ -58,7 +58,6 @@ static inline void unlock_audio(void)
58 pthread_mutex_unlock(&audio_lock_mutex); 58 pthread_mutex_unlock(&audio_lock_mutex);
59} 59}
60 60
61
62/* 61/*
63 * write pcm samples to the hardware. Calls AudioTrack.write directly (which 62 * write pcm samples to the hardware. Calls AudioTrack.write directly (which
64 * is usually a blocking call) 63 * is usually a blocking call)
@@ -93,18 +92,23 @@ Java_org_rockbox_RockboxPCM_nativeWrite(JNIEnv *env, jobject this,
93 (*env)->SetByteArrayRegion(env, temp_array, 0, 92 (*env)->SetByteArrayRegion(env, temp_array, 0,
94 transfer_size, (jbyte*)pcm_data_start); 93 transfer_size, (jbyte*)pcm_data_start);
95 94
96 ret = (*env)->CallIntMethod(env, this, write_method,
97 temp_array, 0, transfer_size);
98
99 if (new_buffer) 95 if (new_buffer)
100 { 96 {
101 new_buffer = false; 97 new_buffer = false;
102 pcm_play_dma_status_callback(PCM_DMAST_STARTED); 98 pcm_play_dma_status_callback(PCM_DMAST_STARTED);
103
104 /* NOTE: might need to release the mutex and sleep here if the
105 buffer is shorter than the required buffer (like pcm-sdl.c) to
106 have the mixer clocked at a regular interval */
107 } 99 }
100 /* SetByteArrayRegion copies, which enables us to unlock audio. This
101 * is good because the below write() call almost certainly block.
102 * This allows the mixer to be clocked at a regular interval which vastly
103 * improves responsiveness when pausing/stopping playback */
104 unlock_audio();
105 ret = (*env)->CallIntMethod(env, this, write_method,
106 temp_array, 0, transfer_size);
107 lock_audio();
108
109 /* check if still playing. might have changed during the write() call */
110 if (!pcm_is_playing())
111 break;
108 112
109 if (ret < 0) 113 if (ret < 0)
110 { 114 {