1 /*
2  * Copyright (c) 2007, 2018, 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.File;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.io.OutputStream;
32 import java.io.RandomAccessFile;
33 
34 /**
35  * Resource Interchange File Format (RIFF) stream encoder.
36  *
37  * @author Karl Helgason
38  */
39 public final class RIFFWriter extends OutputStream {
40 
41     private interface RandomAccessWriter {
42 
seek(long chunksizepointer)43         void seek(long chunksizepointer) throws IOException;
44 
getPointer()45         long getPointer() throws IOException;
46 
close()47         void close() throws IOException;
48 
write(int b)49         void write(int b) throws IOException;
50 
write(byte[] b, int off, int len)51         void write(byte[] b, int off, int len) throws IOException;
52 
write(byte[] bytes)53         void write(byte[] bytes) throws IOException;
54 
length()55         long length() throws IOException;
56 
setLength(long i)57         void setLength(long i) throws IOException;
58     }
59 
60     private static class RandomAccessFileWriter implements RandomAccessWriter {
61 
62         RandomAccessFile raf;
63 
RandomAccessFileWriter(File file)64         RandomAccessFileWriter(File file) throws FileNotFoundException {
65             this.raf = new RandomAccessFile(file, "rw");
66         }
67 
RandomAccessFileWriter(String name)68         RandomAccessFileWriter(String name) throws FileNotFoundException {
69             this.raf = new RandomAccessFile(name, "rw");
70         }
71 
72         @Override
seek(long chunksizepointer)73         public void seek(long chunksizepointer) throws IOException {
74             raf.seek(chunksizepointer);
75         }
76 
77         @Override
getPointer()78         public long getPointer() throws IOException {
79             return raf.getFilePointer();
80         }
81 
82         @Override
close()83         public void close() throws IOException {
84             raf.close();
85         }
86 
87         @Override
write(int b)88         public void write(int b) throws IOException {
89             raf.write(b);
90         }
91 
92         @Override
write(byte[] b, int off, int len)93         public void write(byte[] b, int off, int len) throws IOException {
94             raf.write(b, off, len);
95         }
96 
97         @Override
write(byte[] bytes)98         public void write(byte[] bytes) throws IOException {
99             raf.write(bytes);
100         }
101 
102         @Override
length()103         public long length() throws IOException {
104             return raf.length();
105         }
106 
107         @Override
setLength(long i)108         public void setLength(long i) throws IOException {
109             raf.setLength(i);
110         }
111     }
112 
113     private static class RandomAccessByteWriter implements RandomAccessWriter {
114 
115         byte[] buff = new byte[32];
116         int length = 0;
117         int pos = 0;
118         byte[] s;
119         final OutputStream stream;
120 
RandomAccessByteWriter(OutputStream stream)121         RandomAccessByteWriter(OutputStream stream) {
122             this.stream = stream;
123         }
124 
125         @Override
seek(long chunksizepointer)126         public void seek(long chunksizepointer) throws IOException {
127             pos = (int) chunksizepointer;
128         }
129 
130         @Override
getPointer()131         public long getPointer() throws IOException {
132             return pos;
133         }
134 
135         @Override
close()136         public void close() throws IOException {
137             stream.write(buff, 0, length);
138             stream.close();
139         }
140 
141         @Override
write(int b)142         public void write(int b) throws IOException {
143             if (s == null)
144                 s = new byte[1];
145             s[0] = (byte)b;
146             write(s, 0, 1);
147         }
148 
149         @Override
write(byte[] b, int off, int len)150         public void write(byte[] b, int off, int len) throws IOException {
151             int newsize = pos + len;
152             if (newsize > length)
153                 setLength(newsize);
154             int end = off + len;
155             for (int i = off; i < end; i++) {
156                 buff[pos++] = b[i];
157             }
158         }
159 
160         @Override
write(byte[] bytes)161         public void write(byte[] bytes) throws IOException {
162             write(bytes, 0, bytes.length);
163         }
164 
165         @Override
length()166         public long length() throws IOException {
167             return length;
168         }
169 
170         @Override
setLength(long i)171         public void setLength(long i) throws IOException {
172             length = (int) i;
173             if (length > buff.length) {
174                 int newlen = Math.max(buff.length << 1, length);
175                 byte[] newbuff = new byte[newlen];
176                 System.arraycopy(buff, 0, newbuff, 0, buff.length);
177                 buff = newbuff;
178             }
179         }
180     }
181     private int chunktype = 0; // 0=RIFF, 1=LIST; 2=CHUNK
182     private RandomAccessWriter raf;
183     private final long chunksizepointer;
184     private final long startpointer;
185     private RIFFWriter childchunk = null;
186     private boolean open = true;
187     private boolean writeoverride = false;
188 
RIFFWriter(String name, String format)189     public RIFFWriter(String name, String format) throws IOException {
190         this(new RandomAccessFileWriter(name), format, 0);
191     }
192 
RIFFWriter(File file, String format)193     public RIFFWriter(File file, String format) throws IOException {
194         this(new RandomAccessFileWriter(file), format, 0);
195     }
196 
RIFFWriter(OutputStream stream, String format)197     public RIFFWriter(OutputStream stream, String format) throws IOException {
198         this(new RandomAccessByteWriter(stream), format, 0);
199     }
200 
RIFFWriter(RandomAccessWriter raf, String format, int chunktype)201     private RIFFWriter(RandomAccessWriter raf, String format, int chunktype)
202             throws IOException {
203         if (chunktype == 0)
204             if (raf.length() != 0)
205                 raf.setLength(0);
206         this.raf = raf;
207         if (raf.getPointer() % 2 != 0)
208             raf.write(0);
209 
210         if (chunktype == 0)
211             raf.write("RIFF".getBytes("ascii"));
212         else if (chunktype == 1)
213             raf.write("LIST".getBytes("ascii"));
214         else
215             raf.write((format + "    ").substring(0, 4).getBytes("ascii"));
216 
217         chunksizepointer = raf.getPointer();
218         this.chunktype = 2;
219         writeUnsignedInt(0);
220         this.chunktype = chunktype;
221         startpointer = raf.getPointer();
222         if (chunktype != 2)
223             raf.write((format + "    ").substring(0, 4).getBytes("ascii"));
224 
225     }
226 
seek(long pos)227     public void seek(long pos) throws IOException {
228         raf.seek(pos);
229     }
230 
getFilePointer()231     public long getFilePointer() throws IOException {
232         return raf.getPointer();
233     }
234 
setWriteOverride(boolean writeoverride)235     public void setWriteOverride(boolean writeoverride) {
236         this.writeoverride = writeoverride;
237     }
238 
getWriteOverride()239     public boolean getWriteOverride() {
240         return writeoverride;
241     }
242 
243     @Override
close()244     public void close() throws IOException {
245         if (!open)
246             return;
247         if (childchunk != null) {
248             childchunk.close();
249             childchunk = null;
250         }
251 
252         int bakchunktype = chunktype;
253         long fpointer = raf.getPointer();
254         raf.seek(chunksizepointer);
255         chunktype = 2;
256         writeUnsignedInt(fpointer - startpointer);
257 
258         if (bakchunktype == 0)
259             raf.close();
260         else
261             raf.seek(fpointer);
262         open = false;
263         raf = null;
264     }
265 
266     @Override
write(int b)267     public void write(int b) throws IOException {
268         if (!writeoverride) {
269             if (chunktype != 2) {
270                 throw new IllegalArgumentException(
271                         "Only chunks can write bytes!");
272             }
273             if (childchunk != null) {
274                 childchunk.close();
275                 childchunk = null;
276             }
277         }
278         raf.write(b);
279     }
280 
281     @Override
write(byte[] b, int off, int len)282     public void write(byte[] b, int off, int len) throws IOException {
283         if (!writeoverride) {
284             if (chunktype != 2) {
285                 throw new IllegalArgumentException(
286                         "Only chunks can write bytes!");
287             }
288             if (childchunk != null) {
289                 childchunk.close();
290                 childchunk = null;
291             }
292         }
293         raf.write(b, off, len);
294     }
295 
writeList(String format)296     public RIFFWriter writeList(String format) throws IOException {
297         if (chunktype == 2) {
298             throw new IllegalArgumentException(
299                     "Only LIST and RIFF can write lists!");
300         }
301         if (childchunk != null) {
302             childchunk.close();
303             childchunk = null;
304         }
305         childchunk = new RIFFWriter(this.raf, format, 1);
306         return childchunk;
307     }
308 
writeChunk(String format)309     public RIFFWriter writeChunk(String format) throws IOException {
310         if (chunktype == 2) {
311             throw new IllegalArgumentException(
312                     "Only LIST and RIFF can write chunks!");
313         }
314         if (childchunk != null) {
315             childchunk.close();
316             childchunk = null;
317         }
318         childchunk = new RIFFWriter(this.raf, format, 2);
319         return childchunk;
320     }
321 
322     // Write ASCII chars to stream
writeString(String string)323     public void writeString(String string) throws IOException {
324         byte[] buff = string.getBytes();
325         write(buff);
326     }
327 
328     // Write ASCII chars to stream
writeString(String string, int len)329     public void writeString(String string, int len) throws IOException {
330         byte[] buff = string.getBytes();
331         if (buff.length > len)
332             write(buff, 0, len);
333         else {
334             write(buff);
335             for (int i = buff.length; i < len; i++)
336                 write(0);
337         }
338     }
339 
340     // Write 8 bit signed integer to stream
writeByte(int b)341     public void writeByte(int b) throws IOException {
342         write(b);
343     }
344 
345     // Write 16 bit signed integer to stream
writeShort(short b)346     public void writeShort(short b) throws IOException {
347         write((b >>> 0) & 0xFF);
348         write((b >>> 8) & 0xFF);
349     }
350 
351     // Write 32 bit signed integer to stream
writeInt(int b)352     public void writeInt(int b) throws IOException {
353         write((b >>> 0) & 0xFF);
354         write((b >>> 8) & 0xFF);
355         write((b >>> 16) & 0xFF);
356         write((b >>> 24) & 0xFF);
357     }
358 
359     // Write 64 bit signed integer to stream
writeLong(long b)360     public void writeLong(long b) throws IOException {
361         write((int) (b >>> 0) & 0xFF);
362         write((int) (b >>> 8) & 0xFF);
363         write((int) (b >>> 16) & 0xFF);
364         write((int) (b >>> 24) & 0xFF);
365         write((int) (b >>> 32) & 0xFF);
366         write((int) (b >>> 40) & 0xFF);
367         write((int) (b >>> 48) & 0xFF);
368         write((int) (b >>> 56) & 0xFF);
369     }
370 
371     // Write 8 bit unsigned integer to stream
writeUnsignedByte(int b)372     public void writeUnsignedByte(int b) throws IOException {
373         writeByte((byte) b);
374     }
375 
376     // Write 16 bit unsigned integer to stream
writeUnsignedShort(int b)377     public void writeUnsignedShort(int b) throws IOException {
378         writeShort((short) b);
379     }
380 
381     // Write 32 bit unsigned integer to stream
writeUnsignedInt(long b)382     public void writeUnsignedInt(long b) throws IOException {
383         writeInt((int) b);
384     }
385 }
386