Run the pcm callback from a separate OS thread, that seems to make audio playback
[maemo-rb.git] / android / src / org / rockbox / RockboxPCM.java
blobeef56f501d49879f34c6d160999ba419826fe559
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2010 Thomas Martitz
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 package org.rockbox;
24 import android.media.AudioFormat;
25 import android.media.AudioManager;
26 import android.media.AudioTrack;
27 import android.os.Handler;
28 import android.os.HandlerThread;
29 import android.os.Process;
30 import android.util.Log;
32 public class RockboxPCM extends AudioTrack
34 byte[] raw_data;
35 private int buf_len;
36 private PCMListener l;
37 private HandlerThread ht;
38 private Handler h = null;
40 private void LOG(CharSequence text)
42 Log.d("Rockbox", (String) text);
45 public RockboxPCM()
47 super(AudioManager.STREAM_MUSIC,
48 44100,
49 /* should be CHANNEL_OUT_STEREO in 2.0 and above */
50 AudioFormat.CHANNEL_CONFIGURATION_STEREO,
51 AudioFormat.ENCODING_PCM_16BIT,
52 24<<10,
53 AudioTrack.MODE_STREAM);
54 ht = new HandlerThread("audio thread", Process.THREAD_PRIORITY_URGENT_AUDIO);
55 ht.start();
56 buf_len = 24<<10; /* in bytes */
58 raw_data = new byte[buf_len]; /* in shorts */
59 for(int i = 0; i < raw_data.length; i++) raw_data[i] = (byte)0;
60 l = new PCMListener(buf_len);
63 int bytes2frames(int bytes) {
64 /* 1 sample is 2 bytes, 2 samples are 1 frame */
65 return (bytes/4);
68 int frames2bytes(int frames) {
69 /* 1 frame is 2 samples, 1 sample is 2 bytes */
70 return (frames*4);
73 @SuppressWarnings("unused")
74 private void play_pause(boolean pause) {
75 if (pause)
77 pause();
79 else
81 if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED)
83 RockboxService.startForeground();
84 if (getState() == AudioTrack.STATE_INITIALIZED)
86 if (h == null)
87 h = new Handler(ht.getLooper());
88 if (setNotificationMarkerPosition(bytes2frames(buf_len)/4) != AudioTrack.SUCCESS)
89 LOG("setNotificationMarkerPosition Error");
90 else
91 setPlaybackPositionUpdateListener(l, h);
93 /* need to fill with silence before starting playback */
94 write(raw_data, frames2bytes(getPlaybackHeadPosition()), raw_data.length);
96 play();
100 @Override
101 public void stop() throws IllegalStateException
103 try {
104 super.stop();
105 } catch (IllegalStateException e) {
106 throw new IllegalStateException(e);
108 RockboxService.stopForeground();
111 @SuppressWarnings("unused")
112 private void set_volume(int volume)
114 /* volume comes from 0..-990 from Rockbox */
115 /* TODO volume is in dB, but this code acts as if it were in %, convert? */
116 float fvolume;
117 /* special case min and max volume to not suffer from floating point accuracy */
118 if (volume == 0)
119 fvolume = 1.0f;
120 else if (volume == -990)
121 fvolume = 0.0f;
122 else
123 fvolume = (volume + 990)/990.0f;
124 setStereoVolume(fvolume, fvolume);
127 public native void pcmSamplesToByteArray(byte[] dest);
129 private class PCMListener implements OnPlaybackPositionUpdateListener {
130 int max_len;
131 int refill_mark;
132 byte[] buf;
133 public PCMListener(int len) {
134 max_len = len;
135 /* refill to 100% when reached the 25% */
136 buf = new byte[max_len*3/4];
137 refill_mark = max_len - buf.length;
139 @Override
140 public void onMarkerReached(AudioTrack track) {
141 // push new data to the hardware
142 RockboxPCM pcm = (RockboxPCM)track;
143 int result = -1;
144 pcm.pcmSamplesToByteArray(buf);
145 result = track.write(buf, 0, buf.length);
146 if (result >= 0)
148 switch(track.getPlayState())
150 case AudioTrack.PLAYSTATE_PLAYING:
151 case AudioTrack.PLAYSTATE_PAUSED:
152 /* refill at 25% no matter of how many bytes we've written */
153 if (setNotificationMarkerPosition(bytes2frames(refill_mark)) != AudioTrack.SUCCESS)
154 LOG("Error in onMarkerReached: Could not set notification marker");
155 else /* recharge */
156 setPlaybackPositionUpdateListener(this, h);
157 break;
158 case AudioTrack.PLAYSTATE_STOPPED:
159 LOG("State STOPPED");
160 break;
163 else
165 LOG("Error in onMarkerReached (result="+result+")");
166 stop();
170 @Override
171 public void onPeriodicNotification(AudioTrack track) {
172 // TODO Auto-generated method stub