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 }