1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
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
;
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
);
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();
74 (AudioManager
) rbservice
.getSystemService(Context
.AUDIO_SERVICE
);
75 maxstreamvolume
= audiomanager
.getStreamMaxVolume(streamtype
);
77 minpcmvolume
= getMinVolume();
78 pcmrange
= getMaxVolume() - minpcmvolume
;
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) /
97 Logger
.d("java:postVolumeChangedEvent, avol "+volume
+" rbvol "+rbvolume
);
98 postVolumeChangedEvent(rbvolume
);
101 private void setupVolumeHandler()
103 BroadcastReceiver broadcastReceiver
= new BroadcastReceiver()
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
&&
115 volume
!= setstreamvolume
&&
116 rbservice
.isRockboxRunning())
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(
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 */
142 private int frames2bytes(int frames
)
144 /* 1 frame is 2 samples, 1 sample is 2 bytes */
148 private void play_pause(boolean pause
)
150 RockboxService service
= RockboxService
.getInstance();
153 Intent widgetUpdate
= new Intent("org.rockbox.UpdateState");
154 widgetUpdate
.putExtra("state", "pause");
155 service
.sendBroadcast(widgetUpdate
);
156 service
.stopForeground();
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
);
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
;
184 setStereoVolume(0, 0);
187 } catch (IllegalStateException e
) {
188 throw new IllegalStateException(e
);
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
;
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
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
;
248 result
= pcm
.nativeWrite(pcm_data
, pcm_data
.length
);
251 switch(getPlayState())
253 case PLAYSTATE_PLAYING
:
254 case PLAYSTATE_PAUSED
:
255 setNotificationMarkerPosition(pcm
.refillmark
);
257 case PLAYSTATE_STOPPED
:
262 else /* stop on error */
266 public void onPeriodicNotification(AudioTrack track
)