/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2010 Thomas Martitz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ package org.rockbox; import java.util.Arrays; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.util.Log; public class RockboxPCM extends AudioTrack { private byte[] raw_data; private PCMListener l; private HandlerThread ht; private Handler h = null; private static final int samplerate = 44100; /* should be CHANNEL_OUT_STEREO in 2.0 and above */ private static final int channels = AudioFormat.CHANNEL_CONFIGURATION_STEREO; private static final int encoding = AudioFormat.ENCODING_PCM_16BIT; /* 24k is plenty, but some devices may have a higher minimum */ private static final int buf_len = Math.max(24<<10, getMinBufferSize(samplerate, channels, encoding)); private void LOG(CharSequence text) { Log.d("Rockbox", (String) text); } public RockboxPCM() { super(AudioManager.STREAM_MUSIC, samplerate, channels, encoding, buf_len, AudioTrack.MODE_STREAM); ht = new HandlerThread("audio thread", Process.THREAD_PRIORITY_URGENT_AUDIO); ht.start(); raw_data = new byte[buf_len]; /* in shorts */ Arrays.fill(raw_data, (byte) 0); l = new PCMListener(buf_len); } private int bytes2frames(int bytes) { /* 1 sample is 2 bytes, 2 samples are 1 frame */ return (bytes/4); } private int frames2bytes(int frames) { /* 1 frame is 2 samples, 1 sample is 2 bytes */ return (frames*4); } @SuppressWarnings("unused") private void play_pause(boolean pause) { if (pause) { pause(); } else { if (getPlayState() == AudioTrack.PLAYSTATE_STOPPED) { RockboxService.get_instance().startForeground(); if (getState() == AudioTrack.STATE_INITIALIZED) { if (h == null) h = new Handler(ht.getLooper()); if (setNotificationMarkerPosition(bytes2frames(buf_len)/4) != AudioTrack.SUCCESS) LOG("setNotificationMarkerPosition Error"); else setPlaybackPositionUpdateListener(l, h); } /* need to fill with silence before starting playback */ write(raw_data, frames2bytes(getPlaybackHeadPosition()), raw_data.length); } play(); } } @Override public void stop() throws IllegalStateException { try { super.stop(); } catch (IllegalStateException e) { throw new IllegalStateException(e); } RockboxService.get_instance().stopForeground(); } @SuppressWarnings("unused") private void set_volume(int volume) { /* volume comes from 0..-990 from Rockbox */ /* TODO: * volume is in dB, but this code acts as if it were in %, convert? */ float fvolume; /* special case min and max volume to not suffer from * floating point accuracy */ if (volume == 0) fvolume = 1.0f; else if (volume == -990) fvolume = 0.0f; else fvolume = (volume + 990)/990.0f; setStereoVolume(fvolume, fvolume); } public native void pcmSamplesToByteArray(byte[] dest); private class PCMListener implements OnPlaybackPositionUpdateListener { private int max_len; private int refill_mark; private byte[] buf; public PCMListener(int len) { max_len = len; /* refill to 100% when reached the 25% */ buf = new byte[max_len*3/4]; refill_mark = max_len - buf.length; } public void onMarkerReached(AudioTrack track) { /* push new data to the hardware */ RockboxPCM pcm = (RockboxPCM)track; int result = -1; pcm.pcmSamplesToByteArray(buf); result = track.write(buf, 0, buf.length); if (result >= 0) { switch(track.getPlayState()) { case AudioTrack.PLAYSTATE_PLAYING: case AudioTrack.PLAYSTATE_PAUSED: /* refill at 25% no matter of how many * bytes we've written */ if (setNotificationMarkerPosition( bytes2frames(refill_mark)) != AudioTrack.SUCCESS) { LOG("Error in onMarkerReached: " + "Could not set notification marker"); } else /* recharge */ setPlaybackPositionUpdateListener(this, h); break; case AudioTrack.PLAYSTATE_STOPPED: LOG("State STOPPED"); break; } } else { LOG("Error in onMarkerReached (result="+result+")"); stop(); } } public void onPeriodicNotification(AudioTrack track) { } } }