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 
nextReadBuffer()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 
nextWriteBuffer()99         public byte[] nextWriteBuffer() {
100             synchronized (buffers_mutex) {
101                 return buffers[writepos % buffers.length];
102             }
103         }
104 
commit()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 
JitterStream(AudioInputStream s, int buffersize, int smallbuffersize)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
close()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
read()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 
fillBuffer()247         public void fillBuffer() {
248             bbuffer = nextReadBuffer();
249             bbuffer_pos = 0;
250         }
251 
252         @Override
read(byte[] b, int off, int len)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
available()273         public int available() {
274             return bbuffer.length - bbuffer_pos;
275         }
276     }
277 
SoftJitterCorrector(AudioInputStream stream, int buffersize, int smallbuffersize)278     public SoftJitterCorrector(AudioInputStream stream, int buffersize,
279             int smallbuffersize) {
280         super(new JitterStream(stream, buffersize, smallbuffersize),
281                 stream.getFormat(), stream.getFrameLength());
282     }
283 }
284