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 package com.sun.media.sound;
  26 
  27 import javax.sound.sampled.AudioFormat;
  28 import javax.sound.sampled.AudioInputStream;
  29 import java.io.EOFException;
  30 import java.io.IOException;
  31 import java.io.InputStream;
  32 
  33 /**
  34  * A jitter corrector to be used with SoftAudioPusher.
  35  *
  36  * @author Karl Helgason
  37  */
  38 public final class SoftJitterCorrector extends AudioInputStream {
  39 
  40     private static class JitterStream extends InputStream {
  41 
  42         static int MAX_BUFFER_SIZE = 1048576;
  43         boolean active = true;
  44         Thread thread;
  45         AudioInputStream stream;
  46         // Cyclic buffer
  47         int writepos = 0;
  48         int readpos = 0;
  49         byte[][] buffers;
  50         private final Object buffers_mutex = new Object();
  51 
  52         // Adapative Drift Statistics
  53         int w_count = 1000;
  54         int w_min_tol = 2;
  55         int w_max_tol = 10;
  56         int w = 0;
  57         int w_min = -1;
  58         // Current read buffer
  59         int bbuffer_pos = 0;
  60         int bbuffer_max = 0;
  61         byte[] bbuffer = null;
  62 
  63         public byte[] nextReadBuffer() {
  64             synchronized (buffers_mutex) {
  65                 if (writepos > readpos) {
  66                     int w_m = writepos - readpos;
  67                     if (w_m < w_min)
  68                         w_min = w_m;
  69 
  70                     int buffpos = readpos;
  71                     readpos++;
  72                     return buffers[buffpos % buffers.length];
  73                 }
  74                 w_min = -1;
  75                 w = w_count - 1;
  76             }
  77             while (true) {
  78                 try {
  79                     Thread.sleep(1);
  80                 } catch (InterruptedException e) {
  81                     //e.printStackTrace();
  82                     return null;
  83                 }
  84                 synchronized (buffers_mutex) {
  85                     if (writepos > readpos) {
  86                         w = 0;
  87                         w_min = -1;
  88                         w = w_count - 1;
  89                         int buffpos = readpos;
  90                         readpos++;
  91                         return buffers[buffpos % buffers.length];
  92                     }
  93                 }
  94             }
  95         }
  96 
  97         public byte[] nextWriteBuffer() {
  98             synchronized (buffers_mutex) {
  99                 return buffers[writepos % buffers.length];
 100             }
 101         }
 102 
 103         public void commit() {
 104             synchronized (buffers_mutex) {
 105                 writepos++;
 106                 if ((writepos - readpos) > buffers.length) {
 107                     int newsize = (writepos - readpos) + 10;
 108                     newsize = Math.max(buffers.length * 2, newsize);
 109                     buffers = new byte[newsize][buffers[0].length];
 110                 }
 111             }
 112         }
 113 
 114         JitterStream(AudioInputStream s, int buffersize,
 115                 int smallbuffersize) {
 116             this.w_count = 10 * (buffersize / smallbuffersize);
 117             if (w_count < 100)
 118                 w_count = 100;
 119             this.buffers
 120                     = new byte[(buffersize/smallbuffersize)+10][smallbuffersize];
 121             this.bbuffer_max = MAX_BUFFER_SIZE / smallbuffersize;
 122             this.stream = s;
 123 
 124 
 125             Runnable runnable = new Runnable() {
 126 
 127                 public void run() {
 128                     AudioFormat format = stream.getFormat();
 129                     int bufflen = buffers[0].length;
 130                     int frames = bufflen / format.getFrameSize();
 131                     long nanos = (long) (frames * 1000000000.0
 132                                             / format.getSampleRate());
 133                     long now = System.nanoTime();
 134                     long next = now + nanos;
 135                     int correction = 0;
 136                     while (true) {
 137                         synchronized (JitterStream.this) {
 138                             if (!active)
 139                                 break;
 140                         }
 141                         int curbuffsize;
 142                         synchronized (buffers) {
 143                             curbuffsize = writepos - readpos;
 144                             if (correction == 0) {
 145                                 w++;
 146                                 if (w_min != Integer.MAX_VALUE) {
 147                                     if (w == w_count) {
 148                                         correction = 0;
 149                                         if (w_min < w_min_tol) {
 150                                             correction = (w_min_tol + w_max_tol)
 151                                                             / 2 - w_min;
 152                                         }
 153                                         if (w_min > w_max_tol) {
 154                                             correction = (w_min_tol + w_max_tol)
 155                                                             / 2 - w_min;
 156                                         }
 157                                         w = 0;
 158                                         w_min = Integer.MAX_VALUE;
 159                                     }
 160                                 }
 161                             }
 162                         }
 163                         while (curbuffsize > bbuffer_max) {
 164                             synchronized (buffers) {
 165                                 curbuffsize = writepos - readpos;
 166                             }
 167                             synchronized (JitterStream.this) {
 168                                 if (!active)
 169                                     break;
 170                             }
 171                             try {
 172                                 Thread.sleep(1);
 173                             } catch (InterruptedException e) {
 174                                 //e.printStackTrace();
 175                             }
 176                         }
 177 
 178                         if (correction < 0)
 179                             correction++;
 180                         else {
 181                             byte[] buff = nextWriteBuffer();
 182                             try {
 183                                 int n = 0;
 184                                 while (n != buff.length) {
 185                                     int s = stream.read(buff, n, buff.length
 186                                             - n);
 187                                     if (s < 0)
 188                                         throw new EOFException();
 189                                     if (s == 0)
 190                                         Thread.yield();
 191                                     n += s;
 192                                 }
 193                             } catch (IOException e1) {
 194                                 //e1.printStackTrace();
 195                             }
 196                             commit();
 197                         }
 198 
 199                         if (correction > 0) {
 200                             correction--;
 201                             next = System.nanoTime() + nanos;
 202                             continue;
 203                         }
 204                         long wait = next - System.nanoTime();
 205                         if (wait > 0) {
 206                             try {
 207                                 Thread.sleep(wait / 1000000L);
 208                             } catch (InterruptedException e) {
 209                                 //e.printStackTrace();
 210                             }
 211                         }
 212                         next += nanos;
 213                     }
 214                 }
 215             };
 216 
 217             thread = new Thread(null, runnable, "JitterCorrector", 0, false);
 218             thread.setDaemon(true);
 219             thread.setPriority(Thread.MAX_PRIORITY);
 220             thread.start();
 221         }
 222 
 223         public void close() throws IOException {
 224             synchronized (this) {
 225                 active = false;
 226             }
 227             try {
 228                 thread.join();
 229             } catch (InterruptedException e) {
 230                 //e.printStackTrace();
 231             }
 232             stream.close();
 233         }
 234 
 235         public int read() throws IOException {
 236             byte[] b = new byte[1];
 237             if (read(b) == -1)
 238                 return -1;
 239             return b[0] & 0xFF;
 240         }
 241 
 242         public void fillBuffer() {
 243             bbuffer = nextReadBuffer();
 244             bbuffer_pos = 0;
 245         }
 246 
 247         public int read(byte[] b, int off, int len) {
 248             if (bbuffer == null)
 249                 fillBuffer();
 250             int bbuffer_len = bbuffer.length;
 251             int offlen = off + len;
 252             while (off < offlen) {
 253                 if (available() == 0)
 254                     fillBuffer();
 255                 else {
 256                     byte[] bbuffer = this.bbuffer;
 257                     int bbuffer_pos = this.bbuffer_pos;
 258                     while (off < offlen && bbuffer_pos < bbuffer_len)
 259                         b[off++] = bbuffer[bbuffer_pos++];
 260                     this.bbuffer_pos = bbuffer_pos;
 261                 }
 262             }
 263             return len;
 264         }
 265 
 266         public int available() {
 267             return bbuffer.length - bbuffer_pos;
 268         }
 269     }
 270 
 271     public SoftJitterCorrector(AudioInputStream stream, int buffersize,
 272             int smallbuffersize) {
 273         super(new JitterStream(stream, buffersize, smallbuffersize),
 274                 stream.getFormat(), stream.getFrameLength());
 275     }
 276 }