mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-10-13 18:17:39 -04:00
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:
parent
9add11d79a
commit
9f242e7be4
2 changed files with 115 additions and 63 deletions
|
@ -43,10 +43,6 @@ public class RockboxPCM extends AudioTrack
|
||||||
AudioFormat.CHANNEL_OUT_STEREO;
|
AudioFormat.CHANNEL_OUT_STEREO;
|
||||||
private static final int encoding =
|
private static final int encoding =
|
||||||
AudioFormat.ENCODING_PCM_16BIT;
|
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 AudioManager audiomanager;
|
||||||
private RockboxService rbservice;
|
private RockboxService rbservice;
|
||||||
private byte[] raw_data;
|
private byte[] raw_data;
|
||||||
|
@ -58,14 +54,20 @@ public class RockboxPCM extends AudioTrack
|
||||||
private float curpcmvolume = 0;
|
private float curpcmvolume = 0;
|
||||||
private float pcmrange;
|
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()
|
public RockboxPCM()
|
||||||
{
|
{
|
||||||
super(streamtype, samplerate, channels, encoding,
|
super(streamtype, samplerate, channels, encoding,
|
||||||
buf_len, AudioTrack.MODE_STREAM);
|
chunkSize, AudioTrack.MODE_STREAM);
|
||||||
HandlerThread ht = new HandlerThread("audio thread",
|
|
||||||
Process.THREAD_PRIORITY_URGENT_AUDIO);
|
streamer = new Streamer(chunkSize);
|
||||||
ht.start();
|
streamer.start();
|
||||||
raw_data = new byte[buf_len]; /* in shorts */
|
raw_data = new byte[chunkSize]; /* in shorts */
|
||||||
Arrays.fill(raw_data, (byte) 0);
|
Arrays.fill(raw_data, (byte) 0);
|
||||||
|
|
||||||
/* find cleaner way to get context? */
|
/* find cleaner way to get context? */
|
||||||
|
@ -79,13 +81,79 @@ public class RockboxPCM extends AudioTrack
|
||||||
|
|
||||||
setupVolumeHandler();
|
setupVolumeHandler();
|
||||||
postVolume(audiomanager.getStreamVolume(streamtype));
|
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();
|
* This class does the actual playback work. Its run() method
|
||||||
setPlaybackPositionUpdateListener(
|
* continuously writes data to the AudioTrack. This operation blocks
|
||||||
new PCMListener(buf_len / 2), new Handler(ht.getLooper()));
|
* and should therefore be run on its own thread.
|
||||||
refillmark = bytes2frames(refillmark);
|
*/
|
||||||
|
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);
|
private native void postVolumeChangedEvent(int volume);
|
||||||
|
@ -164,7 +232,6 @@ public class RockboxPCM extends AudioTrack
|
||||||
service.startForeground();
|
service.startForeground();
|
||||||
if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED)
|
if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED)
|
||||||
{
|
{
|
||||||
setNotificationMarkerPosition(refillmark);
|
|
||||||
/* need to fill with silence before starting playback */
|
/* need to fill with silence before starting playback */
|
||||||
write(raw_data, 0, raw_data.length);
|
write(raw_data, 0, raw_data.length);
|
||||||
}
|
}
|
||||||
|
@ -172,6 +239,15 @@ public class RockboxPCM extends AudioTrack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public synchronized void stop() throws IllegalStateException
|
public synchronized void stop() throws IllegalStateException
|
||||||
{
|
{
|
||||||
|
@ -196,6 +272,14 @@ public class RockboxPCM extends AudioTrack
|
||||||
RockboxService.getInstance().stopForeground();
|
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)
|
public int setStereoVolume(float leftVolume, float rightVolume)
|
||||||
{
|
{
|
||||||
curpcmvolume = leftVolume;
|
curpcmvolume = leftVolume;
|
||||||
|
@ -231,40 +315,4 @@ public class RockboxPCM extends AudioTrack
|
||||||
}
|
}
|
||||||
|
|
||||||
public native int nativeWrite(byte[] temp, int len);
|
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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,6 @@ static inline void unlock_audio(void)
|
||||||
pthread_mutex_unlock(&audio_lock_mutex);
|
pthread_mutex_unlock(&audio_lock_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* write pcm samples to the hardware. Calls AudioTrack.write directly (which
|
* write pcm samples to the hardware. Calls AudioTrack.write directly (which
|
||||||
* is usually a blocking call)
|
* is usually a blocking call)
|
||||||
|
@ -93,18 +92,23 @@ Java_org_rockbox_RockboxPCM_nativeWrite(JNIEnv *env, jobject this,
|
||||||
(*env)->SetByteArrayRegion(env, temp_array, 0,
|
(*env)->SetByteArrayRegion(env, temp_array, 0,
|
||||||
transfer_size, (jbyte*)pcm_data_start);
|
transfer_size, (jbyte*)pcm_data_start);
|
||||||
|
|
||||||
ret = (*env)->CallIntMethod(env, this, write_method,
|
|
||||||
temp_array, 0, transfer_size);
|
|
||||||
|
|
||||||
if (new_buffer)
|
if (new_buffer)
|
||||||
{
|
{
|
||||||
new_buffer = false;
|
new_buffer = false;
|
||||||
pcm_play_dma_status_callback(PCM_DMAST_STARTED);
|
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)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue