1 /* 2 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.sound; 27 28 import java.io.EOFException; 29 import java.io.IOException; 30 import java.io.InputStream; 31 32 import javax.sound.sampled.AudioFormat; 33 import javax.sound.sampled.AudioInputStream; 34 35 /** 36 * A jitter corrector to be used with SoftAudioPusher. 37 * 38 * @author Karl Helgason 39 */ 40 public final class SoftJitterCorrector extends AudioInputStream { 41 42 private static class JitterStream extends InputStream { 43 44 static int MAX_BUFFER_SIZE = 1048576; 45 boolean active = true; 46 Thread thread; 47 AudioInputStream stream; 48 // Cyclic buffer 49 int writepos = 0; 50 int readpos = 0; 51 byte[][] buffers; 52 private final Object buffers_mutex = new Object(); 53 54 // Adapative Drift Statistics 55 int w_count = 1000; 56 int w_min_tol = 2; 57 int w_max_tol = 10; 58 int w = 0; 59 int w_min = -1; 60 // Current read buffer 61 int bbuffer_pos = 0; 62 int bbuffer_max = 0; 63 byte[] bbuffer = null; 64 65 public byte[] nextReadBuffer() { 66 synchronized (buffers_mutex) { 67 if (writepos > readpos) { 68 int w_m = writepos - readpos; 69 if (w_m < w_min) 70 w_min = w_m; 71 72 int buffpos = readpos; 73 readpos++; 74 return buffers[buffpos % buffers.length]; 75 } 76 w_min = -1; 77 w = w_count - 1; 78 } 79 while (true) { 80 try { 81 Thread.sleep(1); 82 } catch (InterruptedException e) { 83 //e.printStackTrace(); 84 return null; 85 } 86 synchronized (buffers_mutex) { 87 if (writepos > readpos) { 88 w = 0; 89 w_min = -1; 90 w = w_count - 1; 91 int buffpos = readpos; 92 readpos++; 93 return buffers[buffpos % buffers.length]; 94 } 95 } 96 } 97 } 98 99 public byte[] nextWriteBuffer() { 100 synchronized (buffers_mutex) { 101 return buffers[writepos % buffers.length]; 102 } 103 } 104 105 public void commit() { 106 synchronized (buffers_mutex) { 107 writepos++; 108 if ((writepos - readpos) > buffers.length) { 109 int newsize = (writepos - readpos) + 10; 110 newsize = Math.max(buffers.length * 2, newsize); 111 buffers = new byte[newsize][buffers[0].length]; 112 } 113 } 114 } 115 116 JitterStream(AudioInputStream s, int buffersize, 117 int smallbuffersize) { 118 this.w_count = 10 * (buffersize / smallbuffersize); 119 if (w_count < 100) 120 w_count = 100; 121 this.buffers 122 = new byte[(buffersize/smallbuffersize)+10][smallbuffersize]; 123 this.bbuffer_max = MAX_BUFFER_SIZE / smallbuffersize; 124 this.stream = s; 125 126 127 Runnable runnable = new Runnable() { 128 129 @Override 130 public void run() { 131 AudioFormat format = stream.getFormat(); 132 int bufflen = buffers[0].length; 133 int frames = bufflen / format.getFrameSize(); 134 long nanos = (long) (frames * 1000000000.0 135 / format.getSampleRate()); 136 long now = System.nanoTime(); 137 long next = now + nanos; 138 int correction = 0; 139 while (true) { 140 synchronized (JitterStream.this) { 141 if (!active) 142 break; 143 } 144 int curbuffsize; 145 synchronized (buffers) { 146 curbuffsize = writepos - readpos; 147 if (correction == 0) { 148 w++; 149 if (w_min != Integer.MAX_VALUE) { 150 if (w == w_count) { 151 correction = 0; 152 if (w_min < w_min_tol) { 153 correction = (w_min_tol + w_max_tol) 154 / 2 - w_min; 155 } 156 if (w_min > w_max_tol) { 157 correction = (w_min_tol + w_max_tol) 158 / 2 - w_min; 159 } 160 w = 0; 161 w_min = Integer.MAX_VALUE; 162 } 163 } 164 } 165 } 166 while (curbuffsize > bbuffer_max) { 167 synchronized (buffers) { 168 curbuffsize = writepos - readpos; 169 } 170 synchronized (JitterStream.this) { 171 if (!active) 172 break; 173 } 174 try { 175 Thread.sleep(1); 176 } catch (InterruptedException e) { 177 //e.printStackTrace(); 178 } 179 } 180 181 if (correction < 0) 182 correction++; 183 else { 184 byte[] buff = nextWriteBuffer(); 185 try { 186 int n = 0; 187 while (n != buff.length) { 188 int s = stream.read(buff, n, buff.length 189 - n); 190 if (s < 0) 191 throw new EOFException(); 192 if (s == 0) 193 Thread.yield(); 194 n += s; 195 } 196 } catch (IOException e1) { 197 //e1.printStackTrace(); 198 } 199 commit(); 200 } 201 202 if (correction > 0) { 203 correction--; 204 next = System.nanoTime() + nanos; 205 continue; 206 } 207 long wait = next - System.nanoTime(); 208 if (wait > 0) { 209 try { 210 Thread.sleep(wait / 1000000L); 211 } catch (InterruptedException e) { 212 //e.printStackTrace(); 213 } 214 } 215 next += nanos; 216 } 217 } 218 }; 219 220 thread = new Thread(null, runnable, "JitterCorrector", 0, false); 221 thread.setDaemon(true); 222 thread.setPriority(Thread.MAX_PRIORITY); 223 thread.start(); 224 } 225 226 @Override 227 public void close() throws IOException { 228 synchronized (this) { 229 active = false; 230 } 231 try { 232 thread.join(); 233 } catch (InterruptedException e) { 234 //e.printStackTrace(); 235 } 236 stream.close(); 237 } 238 239 @Override 240 public int read() throws IOException { 241 byte[] b = new byte[1]; 242 if (read(b) == -1) 243 return -1; 244 return b[0] & 0xFF; 245 } 246 247 public void fillBuffer() { 248 bbuffer = nextReadBuffer(); 249 bbuffer_pos = 0; 250 } 251 252 @Override 253 public int read(byte[] b, int off, int len) { 254 if (bbuffer == null) 255 fillBuffer(); 256 int bbuffer_len = bbuffer.length; 257 int offlen = off + len; 258 while (off < offlen) { 259 if (available() == 0) 260 fillBuffer(); 261 else { 262 byte[] bbuffer = this.bbuffer; 263 int bbuffer_pos = this.bbuffer_pos; 264 while (off < offlen && bbuffer_pos < bbuffer_len) 265 b[off++] = bbuffer[bbuffer_pos++]; 266 this.bbuffer_pos = bbuffer_pos; 267 } 268 } 269 return len; 270 } 271 272 @Override 273 public int available() { 274 return bbuffer.length - bbuffer_pos; 275 } 276 } 277 278 public SoftJitterCorrector(AudioInputStream stream, int buffersize, 279 int smallbuffersize) { 280 super(new JitterStream(stream, buffersize, smallbuffersize), 281 stream.getFormat(), stream.getFrameLength()); 282 } 283 }