[Slim-Checkins] r12765 - in /branches/6.5/softsqueeze/src/org/titmuss/softsqueeze: audio/AudioMixer.java audio/Player.java net/Protocol.java
andy at svn.slimdevices.com
andy at svn.slimdevices.com
Tue Aug 28 11:48:45 PDT 2007
Author: andy
Date: Tue Aug 28 11:48:45 2007
New Revision: 12765
URL: http://svn.slimdevices.com?rev=12765&view=rev
Log:
Sync patches to Softsqueeze from Alan Young. In the 6.5 branch because the trunk Softsqueeze appears broken
Modified:
branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/audio/AudioMixer.java
branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/audio/Player.java
branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/net/Protocol.java
Modified: branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/audio/AudioMixer.java
URL: http://svn.slimdevices.com/branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/audio/AudioMixer.java?rev=12765&r1=12764&r2=12765&view=diff
==============================================================================
--- branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/audio/AudioMixer.java (original)
+++ branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/audio/AudioMixer.java Tue Aug 28 11:48:45 2007
@@ -111,6 +111,8 @@
private long bufDuration = 0;
private int slowStart = 0;
+
+ private int skipFrames = 0;
/**
@@ -160,6 +162,12 @@
line.open(audioFormat, lineBufferSize);
framePositionOffset = 0;
+ if (logger.isDebugEnabled()) {
+ javax.sound.sampled.Control[] ctls = line.getControls();
+ for (int i = 0; i < ctls.length; i++)
+ logger.debug("Control: " + ctls[i]);
+ }
+
gainControl = (FloatControl) (line.getControl(FloatControl.Type.MASTER_GAIN));
gainControl.setValue(dB);
@@ -206,7 +214,7 @@
this.visualizer = newVisualizer;
}
- public long getElapsedSeconds() {
+ public long getElapsedMilliseconds() {
long framePos = line.getFramePosition() - framePositionOffset;
if (framePos < lastFramePos) {
lastFramePos = framePos;
@@ -216,20 +224,30 @@
lastFramePos = framePos;
}
- return (long) ((double)framePos / frameRate);
+ return (long) ((double)framePos / frameRate * 1000);
}
/**
* Stop the audio line.
*/
- public synchronized void pause() {
+ public synchronized void pause(int interval) {
if (!line.isRunning())
return;
- logger.debug("pause line inState=" + inState);
+ logger.debug("pause line inState=" + inState + ", interval=" + interval);
line.stop();
- toState = PAUSE;
+ if (interval != 0) {
+ try {
+ Thread.sleep(interval+2000);
+ } catch (InterruptedException e) {
+ logger.debug("pause sleep interrupted");
+ } finally {
+ line.start();
+ }
+ } else
+ toState = PAUSE;
+
notifyAll();
}
@@ -237,14 +255,30 @@
* Start the audio line. Data written to the line will
* now be played.
*/
- public synchronized void play() {
+ public synchronized void play(long atTime) {
if (line.isRunning())
return;
logger.debug("play line inState=" + inState);
+
+ toState = PLAY;
+
+ if (atTime != 0) {
+ long interval = atTime - System.currentTimeMillis();
+ // Only sleep if we are not already too late and within 2s
+ // Don't bother trying to deal with the jiffies-wrap-around case
+ while (toState == PLAY && interval > 0 && interval < 2000) {
+ try {
+ notifyAll(); // let the mixer thread start filling the line buffer
+ wait(interval);
+ } catch (InterruptedException e) {
+ }
+ interval = atTime - System.currentTimeMillis();
+ }
+ }
+
line.start();
- toState = PLAY;
notifyAll();
}
@@ -301,10 +335,19 @@
line.drain();
line.stop();
+ skipFrames = 0;
toState = PAUSE;
notifyAll();
mixerThread.interrupt(); // Stop audio buffer blocking
+ }
+
+ public synchronized void skipAhead(int msInterval) {
+ logger.debug("skipAhead " + msInterval + " frames left to skip=" + skipFrames);
+ if (skipFrames > 0 || inState != PLAY)
+ return; // not playing or not finished previous skip yet
+
+ skipFrames = (int)(frameRate * msInterval / 1000);
}
/**
@@ -349,10 +392,10 @@
}
logger.debug("audio mixer playing available="+audioBuffer.available());
- /* about to play, start audio line */
+ /* about to play, audio line started by play() */
bufDuration = 0;
slowStart = 4096;
- line.start();
+ // line.start();
if (visualizer != null)
visualizer.play();
}
@@ -363,6 +406,7 @@
line.stop();
line.close();
+ skipFrames = 0;
initLine();
if (inState == PLAY)
@@ -405,6 +449,7 @@
line.stop();
line.flush();
bufLen = 0;
+ skipFrames = 0;
audioBuffer.flush();
framePositionOffset = line.getFramePosition();
@@ -426,38 +471,61 @@
*/
int lineAvail = line.available();
- int fillLen = Math.min(bufSize + lineAvail, buf.length - bufLen);
+ int fillLen;
boolean fillBuf = slowStart < lineSize;
- if (fillBuf)
- fillLen = Math.min(slowStart, fillLen);
-
- int br = 0;
- if (fillLen > 0) {
- try {
- br = audioBuffer.read(buf, bufLen, fillLen);
- if (br < 0)
- return br; /* eof */
-
- bufLen += br;
- }
- catch (IOException e) {
- br = 0; // read interrupted in drain
- }
- }
- else {
- if (br < fillLen)
- logger.debug("playFrame: short read br=" + br + " fillLen=" + fillLen);
- }
-
- if (vlogger.isDebugEnabled())
- vlogger.debug("playFrame: bytes read=" + br + " bufLen=" + bufLen + " fillLen=" + fillLen + " available=" + ((int)((lineAvail/(float)lineSize)*100.0)) + "%");
+
+ do {
+ fillLen = buf.length - bufLen;
+ if (!line.isRunning() && (fillLen + bufLen) > lineAvail)
+ fillLen = lineAvail - bufLen;
+ else if (fillBuf)
+ fillLen = Math.min(slowStart, fillLen);
+
+ int br = 0;
+ if (fillLen > 0) {
+ try {
+ br = audioBuffer.read(buf, bufLen, fillLen);
+ if (br < 0)
+ return br; /* eof */
+
+ bufLen += br;
+
+ if (br < fillLen)
+ logger.debug("playFrame: short read br=" + br + " fillLen=" + fillLen);
+ }
+ catch (IOException e) {
+ br = 0; // read interrupted in drain
+ }
+ }
+ if (vlogger.isDebugEnabled())
+ vlogger.debug("playFrame: bytes read=" + br
+ + " bufLen=" + bufLen + " fillLen=" + fillLen
+ + " available=" + ((int)((lineAvail/(float)lineSize)*100.0)) + "%");
+
+ if (skipFrames > 0) {
+ int skipBytes = skipFrames * frameSize;
+ if (skipBytes > bufLen)
+ skipBytes = bufLen - (bufLen % frameSize);
+ if (skipBytes > 0) {
+ if (skipBytes < bufLen) {
+ System.arraycopy(buf, skipBytes, buf, 0, bufLen - skipBytes);
+ bufLen -= skipBytes;
+ } else
+ bufLen = 0;
+ int skippedFrames = skipBytes / frameSize;
+ skipFrames -= skippedFrames;
+ framePositionOffset -= skippedFrames;
+ }
+ }
+
+ } while (skipFrames > 0);
readTime = System.currentTimeMillis();
long readElapsed = readTime - writeTime;
int bw = 0;
- if (inState == PLAY) {
+ if (inState == PLAY || inState == PAUSE) {
/* Write the buffer to the line, this may block if the line if full */
bw = line.write(buf, 0, bufLen);
if (bw < 0)
Modified: branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/audio/Player.java
URL: http://svn.slimdevices.com/branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/audio/Player.java?rev=12765&r1=12764&r2=12765&view=diff
==============================================================================
--- branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/audio/Player.java (original)
+++ branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/audio/Player.java Tue Aug 28 11:48:45 2007
@@ -102,6 +102,8 @@
private boolean loopSong;
private float replayGain;
+
+ private int interval;
private String ipaddr;
@@ -147,6 +149,7 @@
squeeze.getProtocol().addProtocolListener("body", this);
squeeze.getProtocol().addProtocolListener("audg", this);
squeeze.getProtocol().addProtocolListener("visu", this);
+ squeeze.getProtocol().addProtocolListener("stat", this);
Config.addConfigListener(this);
@@ -187,7 +190,7 @@
audioMixer.setVisualizer(visualizer);
if (state == PLAYING)
- audioMixer.play();
+ audioMixer.play(0);
}
catch (AudioException e) {
logger.error("Changing mixer", e);
@@ -202,30 +205,48 @@
*/
public void slimprotoCmd(String cmd, byte[] buf, int off, int len) {
if (cmd.equals("strm")) {
- String scmd = parseStream(buf, off, len);
+ char scmd = parseStream(buf, off, len);
try {
- if (scmd.equals("s")) { // start
- connect();
- autostart();
- } else if (scmd.equals("u")) { // unpause
- start();
- } else if (scmd.equals("p")) { // pause
- pause();
- } else if (scmd.equals("q")) { // quit
- disconnect();
- } else if (scmd.equals("f")) { // flush
- flush();
- } else if (scmd.equals("t")) { // status
- sendStatus("STMt");
- } else {
- logger.warn("Unknown strm command " + scmd);
+ switch (scmd) {
+
+ case 's': // start
+ connect();
+ autostart();
+ break;
+ case 'u': // unpause
+ start(interval);
+ break;
+ case 'p': // pause
+ pause(interval);
+ break;
+ case 'q': // quit
+ disconnect();
+ break;
+ case 'f': // flush
+ flush();
+ break;
+ case 't': // status
+ sendStatus("STMt");
+ break;
+ case 'a': // status
+ skipAhead(interval);
+ break;
+ default:
+ logger.warn("Unknown strm command " + scmd);
}
} catch (IOException e) {
logger.error("strm IO error", e);
} catch (AudioException e) {
logger.error("strm Audio error", e);
}
+ }
+ else if (cmd.equals("stat")) {
+ try {
+ sendStatus("STMt");
+ } catch (IOException e) {
+ logger.error("stat IO error", e);
+ }
}
else if (cmd.equals("cont")) {
try {
@@ -609,7 +630,7 @@
sendStatus("STMl");
}
else {
- start();
+ start(0);
}
}
@@ -618,8 +639,8 @@
* @throws IOException
* @throws AudioException
*/
- private void start() throws IOException, AudioException {
- logger.debug("start: state " + state);
+ private void start(int atJiffies) throws IOException, AudioException {
+ logger.debug("start: state " + state + ", atJiffies=" + atJiffies);
synchronized (lock) {
if (state == PLAYING)
@@ -628,7 +649,7 @@
if (state == DISCONNECTED)
connect();
- audioMixer.play();
+ audioMixer.play(atJiffies != 0 ? squeeze.getProtocol().getEpoch() + atJiffies : 0);
// sendStatus("STMa"); /* not used by Squeezebox2 */
state = PLAYING;
@@ -644,7 +665,7 @@
logger.debug("start: state " + state);
synchronized (lock) {
- audioMixer.play();
+ audioMixer.play(0);
sendStatus("STMr");
state = PLAYING;
@@ -656,23 +677,48 @@
* Pause the player.
* @throws IOException
*/
- private void pause() throws IOException {
- logger.debug("pause: state " + state);
+ private void pause(int interval) throws IOException {
+ logger.debug("pause: state " + state + ", interval " + interval);
+
+ synchronized (lock) {
+ if (state != PLAYING)
+ return;
+
+ audioMixer.pause(interval);
+
+ if (interval == 0) {
+ sendStatus("STMp");
+ state = PAUSED;
+ }
+
+ lock.notifyAll();
+ }
+ }
+
+
+ /**
+ * Pause the player.
+ * @throws IOException
+ */
+ private void skipAhead(int interval) throws IOException {
+ logger.debug("skipahead: interval " + interval);
synchronized (lock) {
if (state != PLAYING)
return;
- audioMixer.pause();
- sendStatus("STMp");
-
- state = PAUSED;
+ audioMixer.skipAhead(interval);
+
lock.notifyAll();
}
}
private void sendStatus(String status) throws IOException {
+ sendStatus(status, 0);
+ }
+
+ private void sendStatus(String status, int timestamp) throws IOException {
byte crlf = (byte) 0; // debug, not used by server
byte masInit = format;
byte masMode = (byte) 1; // debug, not used by server
@@ -700,7 +746,7 @@
long bytesRx = (decoderBuffer == null) ? 0 : decoderBuffer.getWriteCount();
byte signal = (byte) 0xFF; // wired squeezebox
int outputFullness = outputBuffer.available();
- int elapsedSeconds = (int) audioMixer.getElapsedSeconds();
+ long elapsedMilliseconds = audioMixer.getElapsedMilliseconds();
statusTime = System.currentTimeMillis();
@@ -708,15 +754,15 @@
if (decoderBuffer != null)
logger.debug("decode: "+((decoderBuffer.available()/(float)decoderBuffer.getBufferSize())*100.0)+" avail="+decoderBuffer.available()+" size="+decoderBuffer.getBufferSize());
logger.debug("output: "+((outputBuffer.available()/(float)outputBuffer.getBufferSize())*100.0)+" avail="+outputBuffer.available()+" size="+outputBuffer.getBufferSize());
- vlogger.debug("status=" + status + " fullness=" + decoderFullness + " bytesRx=" + bytesRx + " elapsedSeconds=" + elapsedSeconds);
+ vlogger.debug("status=" + status + " fullness=" + decoderFullness + " bytesRx=" + bytesRx + " elapsedMilliseconds=" + elapsedMilliseconds);
}
else if (!status.equals("STMt") && logger.isDebugEnabled()) {
- logger.debug("status=" + status + " fullness=" + decoderFullness + " bytesRx=" + bytesRx + " elapsedSeconds=" + elapsedSeconds);
+ logger.debug("status=" + status + " fullness=" + decoderFullness + " bytesRx=" + bytesRx + " elapsedMilliseconds=" + elapsedMilliseconds);
}
squeeze.getProtocol().sendStat(status, crlf, masInit, masMode,
DECODER_BUFFER_SIZE, decoderFullness, bytesRx, signal,
- OUTPUT_BUFFER_SIZE, outputFullness, elapsedSeconds);
+ OUTPUT_BUFFER_SIZE, outputFullness, elapsedMilliseconds, timestamp);
}
private class PlayerStatus implements Runnable {
@@ -789,7 +835,7 @@
}
if (buffer == decoderBuffer && state == BUFFERING) {
logger.debug("audio stream closed while buffering, starting playback");
- start();
+ start(0);
}
break;
@@ -819,9 +865,8 @@
return (int)decoderBuffer.getWriteCount();
}
- private String parseStream(byte buf[], int start, int len) {
- String cmd = new String(buf, start, 1);
- byte autostartFlag = buf[start + 1];
+ private char parseStream(byte buf[], int start, int len) {
+ char cmd = (char)buf[start]; byte autostartFlag = buf[start + 1];
autostart = (autostartFlag == '1' || autostartFlag == '3');
directStream = (autostartFlag == '2' || autostartFlag == '3' || autostartFlag == '4');
format = buf[start + 2];
@@ -835,10 +880,20 @@
transitionType = buf[start + 10];
loopSong = (buf[11] == '1');
// buf[start + 12]; reserved
- replayGain = Protocol.unpackFixedPoint(buf, start + 14);
- if (replayGain == 0.0f)
- replayGain = 1.0f; // Use 0.00dB with no replay gain
+ switch (cmd) {
+ case 's':
+ replayGain = Protocol.unpackFixedPoint(buf, start + 14);
+ if (replayGain == 0.0f)
+ replayGain = 1.0f; // Use 0.00dB with no replay gain
+ break;
+ case 'p':
+ case 'u':
+ case 'a':
+ case 't':
+ interval = Protocol.unpackN4(buf, start + 14);
+ break;
+ }
// reserved
port = Protocol.unpackN2(buf, start + 18);
Modified: branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/net/Protocol.java
URL: http://svn.slimdevices.com/branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/net/Protocol.java?rev=12765&r1=12764&r2=12765&view=diff
==============================================================================
--- branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/net/Protocol.java (original)
+++ branches/6.5/softsqueeze/src/org/titmuss/softsqueeze/net/Protocol.java Tue Aug 28 11:48:45 2007
@@ -244,12 +244,12 @@
*/
public void sendStat(String code, byte crlf, byte masInit, byte masMode,
int rptr, int wptr, long bytesRx, byte wirelessSignal,
- int outputBufferSize, int outputBufferFullness, int elapsedSeconds) {
- if (!isConnected() || !helosent)
- return;
-
- try {
- byte args[] = new byte[41];
+ int outputBufferSize, int outputBufferFullness, long elapsedMilliseconds, int timestamp) {
+ if (!isConnected() || !helosent)
+ return;
+
+ try {
+ byte args[] = new byte[51];
System.arraycopy(code.getBytes(), 0, args, 0, 4);
args[4] = crlf;
args[5] = masInit;
@@ -261,7 +261,10 @@
packN4(args, 25, getJiffies());
packN4(args, 29, outputBufferSize);
packN4(args, 33, outputBufferFullness);
- packN4(args, 37, elapsedSeconds);
+ packN4(args, 37, (int)(elapsedMilliseconds/1000));
+ packN4(args, 41, 0); // voltage
+ packN4(args, 43, (int)elapsedMilliseconds);
+ packN4(args, 47, timestamp);
sendCommand("STAT", args);
} catch (IOException e) {
@@ -641,5 +644,9 @@
buf[pos++] = (byte) ((arg >> 8) & 0xFF);
buf[pos++] = (byte) ((arg >> 0) & 0xFF);
}
+
+ public long getEpoch() {
+ return epoch;
+ }
}
More information about the checkins
mailing list