Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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>
  • Loading branch information...
commit 9f242e7be4f301e965d0bf35908a9bcaacdfdcae 1 parent 9add11d
Thomas Martitz kugel- authored
158 android/src/org/rockbox/RockboxPCM.java
View
@@ -43,10 +43,6 @@
AudioFormat.CHANNEL_OUT_STEREO;
private static final int encoding =
AudioFormat.ENCODING_PCM_16BIT;
- /* 32k is plenty, but some devices may have a higher minimum */
- private static final int buf_len =
- Math.max(32<<10, 4*getMinBufferSize(samplerate, channels, encoding));
-
private AudioManager audiomanager;
private RockboxService rbservice;
private byte[] raw_data;
@@ -58,14 +54,20 @@
private float curpcmvolume = 0;
private float pcmrange;
+ /* 8k is plenty, but some devices may have a higher minimum.
+ * 8k represents 125ms of audio */
+ private static final int chunkSize =
+ Math.max(8<<10, getMinBufferSize(samplerate, channels, encoding));
+ Streamer streamer;
+
public RockboxPCM()
{
super(streamtype, samplerate, channels, encoding,
- buf_len, AudioTrack.MODE_STREAM);
- HandlerThread ht = new HandlerThread("audio thread",
- Process.THREAD_PRIORITY_URGENT_AUDIO);
- ht.start();
- raw_data = new byte[buf_len]; /* in shorts */
+ chunkSize, AudioTrack.MODE_STREAM);
+
+ streamer = new Streamer(chunkSize);
+ streamer.start();
+ raw_data = new byte[chunkSize]; /* in shorts */
Arrays.fill(raw_data, (byte) 0);
/* find cleaner way to get context? */
@@ -79,14 +81,80 @@ public RockboxPCM()
setupVolumeHandler();
postVolume(audiomanager.getStreamVolume(streamtype));
- refillmark = buf_len / 4; /* higher values don't work on many devices */
+ }
- /* getLooper() returns null if thread isn't running */
- while(!ht.isAlive()) Thread.yield();
- setPlaybackPositionUpdateListener(
- new PCMListener(buf_len / 2), new Handler(ht.getLooper()));
- refillmark = bytes2frames(refillmark);
- }
+ /**
+ * This class does the actual playback work. Its run() method
+ * continuously writes data to the AudioTrack. This operation blocks
+ * and should therefore be run on its own thread.
+ */
+ private class Streamer extends Thread
+ {
+ byte[] buffer;
+ private boolean quit = false;
+
+ Streamer(int bufsize)
+ {
+ super("audio thread");
+ buffer = new byte[bufsize];
+ }
+
+ @Override
+ public void run()
+ {
+ /* THREAD_PRIORITY_URGENT_AUDIO can only be specified via
+ * setThreadPriority(), and not via thread.setPriority(). This is
+ * also how the android's HandlerThread class implements it */
+ Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
+ while (!quit)
+ {
+ switch(getPlayState())
+ {
+ case PLAYSTATE_PLAYING:
+ nativeWrite(buffer, buffer.length);
+ break;
+ case PLAYSTATE_PAUSED:
+ case PLAYSTATE_STOPPED:
+ {
+ synchronized (this)
+ {
+ try
+ {
+ wait();
+ }
+ catch (InterruptedException e) { e.printStackTrace(); }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ synchronized void quit()
+ {
+ quit = true;
+ notify();
+ }
+
+ synchronized void kick()
+ {
+ notify();
+ }
+
+ void quitAndJoin()
+ {
+ while(true)
+ {
+ try
+ {
+ quit();
+ join();
+ return;
+ }
+ catch (InterruptedException e) { }
+ }
+ }
+ }
private native void postVolumeChangedEvent(int volume);
@@ -164,14 +232,22 @@ private void play_pause(boolean pause)
service.startForeground();
if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED)
{
- setNotificationMarkerPosition(refillmark);
/* need to fill with silence before starting playback */
write(raw_data, 0, raw_data.length);
}
play();
}
}
-
+
+ @Override
+ public void play() throws IllegalStateException
+ {
+ super.play();
+ /* when stopped or paused the streamer is in a wait() state. need
+ * it to wake it up */
+ streamer.kick();
+ }
+
@Override
public synchronized void stop() throws IllegalStateException
{
@@ -195,7 +271,15 @@ public synchronized void stop() throws IllegalStateException
RockboxService.getInstance().sendBroadcast(widgetUpdate);
RockboxService.getInstance().stopForeground();
}
-
+
+ @Override
+ public void release()
+ {
+ super.release();
+ /* stop streamer if this AudioTrack is destroyed by whomever */
+ streamer.quitAndJoin();
+ }
+
public int setStereoVolume(float leftVolume, float rightVolume)
{
curpcmvolume = leftVolume;
@@ -231,40 +315,4 @@ private void set_volume(int volume)
}
public native int nativeWrite(byte[] temp, int len);
-
- private class PCMListener implements OnPlaybackPositionUpdateListener
- {
- byte[] pcm_data;
- public PCMListener(int _refill_bufsize)
- {
- pcm_data = new byte[_refill_bufsize];
- }
-
- public void onMarkerReached(AudioTrack track)
- {
- /* push new data to the hardware */
- RockboxPCM pcm = (RockboxPCM)track;
- int result = -1;
- result = pcm.nativeWrite(pcm_data, pcm_data.length);
- if (result >= 0)
- {
- switch(getPlayState())
- {
- case PLAYSTATE_PLAYING:
- case PLAYSTATE_PAUSED:
- setNotificationMarkerPosition(pcm.refillmark);
- break;
- case PLAYSTATE_STOPPED:
- Logger.d("Stopped");
- break;
- }
- }
- else /* stop on error */
- stop();
- }
-
- public void onPeriodicNotification(AudioTrack track)
- {
- }
- }
}
20 firmware/target/hosted/android/pcm-android.c
View
@@ -58,7 +58,6 @@ static inline void unlock_audio(void)
pthread_mutex_unlock(&audio_lock_mutex);
}
-
/*
* write pcm samples to the hardware. Calls AudioTrack.write directly (which
* is usually a blocking call)
@@ -93,18 +92,23 @@ Java_org_rockbox_RockboxPCM_nativeWrite(JNIEnv *env, jobject this,
(*env)->SetByteArrayRegion(env, temp_array, 0,
transfer_size, (jbyte*)pcm_data_start);
- ret = (*env)->CallIntMethod(env, this, write_method,
- temp_array, 0, transfer_size);
-
if (new_buffer)
{
new_buffer = false;
pcm_play_dma_status_callback(PCM_DMAST_STARTED);
-
- /* NOTE: might need to release the mutex and sleep here if the
- buffer is shorter than the required buffer (like pcm-sdl.c) to
- have the mixer clocked at a regular interval */
}
+ /* SetByteArrayRegion copies, which enables us to unlock audio. This
+ * is good because the below write() call almost certainly block.
+ * This allows the mixer to be clocked at a regular interval which vastly
+ * improves responsiveness when pausing/stopping playback */
+ unlock_audio();
+ ret = (*env)->CallIntMethod(env, this, write_method,
+ temp_array, 0, transfer_size);
+ lock_audio();
+
+ /* check if still playing. might have changed during the write() call */
+ if (!pcm_is_playing())
+ break;
if (ret < 0)
{
Please sign in to comment.
Something went wrong with that request. Please try again.