[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