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>
This commit is contained in:
Thomas Martitz 2013-03-16 22:35:54 +01:00
parent 9add11d79a
commit 9f242e7be4
2 changed files with 115 additions and 63 deletions

View file

@ -43,10 +43,6 @@ public class RockboxPCM extends AudioTrack
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 @@ public class RockboxPCM extends AudioTrack
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 class RockboxPCM extends AudioTrack
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 @@ public class RockboxPCM extends AudioTrack
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 class RockboxPCM extends AudioTrack
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 @@ public class RockboxPCM extends AudioTrack
}
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)
{
}
}
}

View file

@ -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)
{