android: clean-up and some refactoring in java code.
[maemo-rb.git] / android / src / org / rockbox / RockboxPCM.java
blobe77a1b00d9f9312719d3cfb71f12d0bb1d492a8a
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 java.util.Arrays;
25 import org.rockbox.Helper.Logger;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.media.AudioFormat;
31 import android.media.AudioManager;
32 import android.media.AudioTrack;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.Process;
37 public class RockboxPCM extends AudioTrack
39 private static final int streamtype = AudioManager.STREAM_MUSIC;
40 private static final int samplerate = 44100;
41 /* should be CHANNEL_OUT_STEREO in 2.0 and above */
42 private static final int channels =
43 AudioFormat.CHANNEL_CONFIGURATION_STEREO;
44 private static final int encoding =
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));
50 private AudioManager audiomanager;
51 private RockboxService rbservice;
52 private byte[] raw_data;
54 private int refillmark;
55 private int maxstreamvolume;
56 private int setstreamvolume = -1;
57 private float minpcmvolume;
58 private float curpcmvolume = 0;
59 private float pcmrange;
61 public RockboxPCM()
63 super(streamtype, samplerate, channels, encoding,
64 buf_len, AudioTrack.MODE_STREAM);
65 HandlerThread ht = new HandlerThread("audio thread",
66 Process.THREAD_PRIORITY_URGENT_AUDIO);
67 ht.start();
68 raw_data = new byte[buf_len]; /* in shorts */
69 Arrays.fill(raw_data, (byte) 0);
71 /* find cleaner way to get context? */
72 rbservice = RockboxService.getInstance();
73 audiomanager =
74 (AudioManager) rbservice.getSystemService(Context.AUDIO_SERVICE);
75 maxstreamvolume = audiomanager.getStreamMaxVolume(streamtype);
77 minpcmvolume = getMinVolume();
78 pcmrange = getMaxVolume() - minpcmvolume;
80 setupVolumeHandler();
81 postVolume(audiomanager.getStreamVolume(streamtype));
82 refillmark = buf_len / 4; /* higher values don't work on many devices */
84 /* getLooper() returns null if thread isn't running */
85 while(!ht.isAlive()) Thread.yield();
86 setPlaybackPositionUpdateListener(
87 new PCMListener(buf_len / 2), new Handler(ht.getLooper()));
88 refillmark = bytes2frames(refillmark);
91 private native void postVolumeChangedEvent(int volume);
93 private void postVolume(int volume)
95 int rbvolume = ((maxstreamvolume - volume) * -99) /
96 maxstreamvolume;
97 Logger.d("java:postVolumeChangedEvent, avol "+volume+" rbvol "+rbvolume);
98 postVolumeChangedEvent(rbvolume);
101 private void setupVolumeHandler()
103 BroadcastReceiver broadcastReceiver = new BroadcastReceiver()
105 @Override
106 public void onReceive(Context context, Intent intent)
108 int streamType = intent.getIntExtra(
109 "android.media.EXTRA_VOLUME_STREAM_TYPE", -1);
110 int volume = intent.getIntExtra(
111 "android.media.EXTRA_VOLUME_STREAM_VALUE", -1);
113 if (streamType == RockboxPCM.streamtype &&
114 volume != -1 &&
115 volume != setstreamvolume &&
116 rbservice.isRockboxRunning())
118 postVolume(volume);
123 /* at startup, change the internal rockbox volume to what the global
124 android music stream volume is */
125 int volume = audiomanager.getStreamVolume(streamtype);
126 int rbvolume = ((maxstreamvolume - volume) * -99) / maxstreamvolume;
127 postVolumeChangedEvent(rbvolume);
129 /* We're relying on internal API's here,
130 this can break in the future! */
131 rbservice.registerReceiver(
132 broadcastReceiver,
133 new IntentFilter("android.media.VOLUME_CHANGED_ACTION"));
136 private int bytes2frames(int bytes)
138 /* 1 sample is 2 bytes, 2 samples are 1 frame */
139 return (bytes/4);
142 private int frames2bytes(int frames)
144 /* 1 frame is 2 samples, 1 sample is 2 bytes */
145 return (frames*4);
148 private void play_pause(boolean pause)
150 RockboxService service = RockboxService.getInstance();
151 if (pause)
153 Intent widgetUpdate = new Intent("org.rockbox.UpdateState");
154 widgetUpdate.putExtra("state", "pause");
155 service.sendBroadcast(widgetUpdate);
156 service.stopForeground();
157 pause();
159 else
161 Intent widgetUpdate = new Intent("org.rockbox.UpdateState");
162 widgetUpdate.putExtra("state", "play");
163 service.sendBroadcast(widgetUpdate);
164 service.startForeground();
165 if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED)
167 setNotificationMarkerPosition(refillmark);
168 /* need to fill with silence before starting playback */
169 write(raw_data, 0, raw_data.length);
171 play();
175 @Override
176 public synchronized void stop() throws IllegalStateException
178 /* flush pending data, but turn the volume off so it cannot be heard.
179 * This is so that we don't hear old data if music is resumed very
180 * quickly after (e.g. when seeking).
182 float old_vol = curpcmvolume;
183 try {
184 setStereoVolume(0, 0);
185 flush();
186 super.stop();
187 } catch (IllegalStateException e) {
188 throw new IllegalStateException(e);
189 } finally {
190 setStereoVolume(old_vol, old_vol);
193 Intent widgetUpdate = new Intent("org.rockbox.UpdateState");
194 widgetUpdate.putExtra("state", "stop");
195 RockboxService.getInstance().sendBroadcast(widgetUpdate);
196 RockboxService.getInstance().stopForeground();
199 public int setStereoVolume(float leftVolume, float rightVolume)
201 curpcmvolume = leftVolume;
202 return super.setStereoVolume(leftVolume, rightVolume);
205 private void set_volume(int volume)
207 Logger.d("java:set_volume("+volume+")");
208 /* Rockbox 'volume' is 0..-990 deci-dB attenuation.
209 Android streams have rather low resolution volume control,
210 typically 8 or 15 steps.
211 Therefore we use the pcm volume to add finer steps between
212 every android stream volume step.
213 It's not "real" dB, but it gives us 100 volume steps.
216 float fraction = 1 - (volume / -990.0f);
217 int streamvolume = (int)Math.ceil(maxstreamvolume * fraction);
218 if (streamvolume > 0) {
219 float streamfraction = (float)streamvolume / maxstreamvolume;
220 float pcmvolume =
221 (fraction / streamfraction) * pcmrange + minpcmvolume;
222 setStereoVolume(pcmvolume, pcmvolume);
225 int oldstreamvolume = audiomanager.getStreamVolume(streamtype);
226 if (streamvolume != oldstreamvolume) {
227 Logger.d("java:setStreamVolume("+streamvolume+")");
228 setstreamvolume = streamvolume;
229 audiomanager.setStreamVolume(streamtype, streamvolume, 0);
233 public native int nativeWrite(byte[] temp, int len);
235 private class PCMListener implements OnPlaybackPositionUpdateListener
237 byte[] pcm_data;
238 public PCMListener(int _refill_bufsize)
240 pcm_data = new byte[_refill_bufsize];
243 public void onMarkerReached(AudioTrack track)
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)
251 switch(getPlayState())
253 case PLAYSTATE_PLAYING:
254 case PLAYSTATE_PAUSED:
255 setNotificationMarkerPosition(pcm.refillmark);
256 break;
257 case PLAYSTATE_STOPPED:
258 Logger.d("Stopped");
259 break;
262 else /* stop on error */
263 stop();
266 public void onPeriodicNotification(AudioTrack track)