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
26package com.sun.media.sound;
27
28import java.io.EOFException;
29import java.io.IOException;
30import java.io.InputStream;
31
32import javax.sound.sampled.AudioFormat;
33import javax.sound.sampled.AudioInputStream;
34
35/**
36 * A jitter corrector to be used with SoftAudioPusher.
37 *
38 * @author Karl Helgason
39 */
40public 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}
284