1 /* 2 * Copyright (c) 2007, 2013, 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 java.io.BufferedInputStream; 28 import java.io.File; 29 import java.io.FileInputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.net.URL; 33 import java.util.HashMap; 34 import java.util.Map; 35 36 import javax.sound.sampled.AudioFileFormat; 37 import javax.sound.sampled.AudioFormat; 38 import javax.sound.sampled.AudioInputStream; 39 import javax.sound.sampled.AudioSystem; 40 import javax.sound.sampled.UnsupportedAudioFileException; 41 import javax.sound.sampled.AudioFormat.Encoding; 42 import javax.sound.sampled.spi.AudioFileReader; 43 44 /** 45 * WAVE file reader for files using format WAVE_FORMAT_EXTENSIBLE (0xFFFE). 46 * 47 * @author Karl Helgason 48 */ 49 public final class WaveExtensibleFileReader extends AudioFileReader { 50 51 static private class GUID { 52 long i1; 53 54 int s1; 55 56 int s2; 57 58 int x1; 59 60 int x2; 61 62 int x3; 63 64 int x4; 65 66 int x5; 67 68 int x6; 69 70 int x7; 71 72 int x8; 73 74 private GUID() { 75 } 76 77 GUID(long i1, int s1, int s2, int x1, int x2, int x3, int x4, 78 int x5, int x6, int x7, int x8) { 79 this.i1 = i1; 80 this.s1 = s1; 81 this.s2 = s2; 82 this.x1 = x1; 83 this.x2 = x2; 84 this.x3 = x3; 85 this.x4 = x4; 86 this.x5 = x5; 87 this.x6 = x6; 88 this.x7 = x7; 89 this.x8 = x8; 90 } 91 92 public static GUID read(RIFFReader riff) throws IOException { 93 GUID d = new GUID(); 94 d.i1 = riff.readUnsignedInt(); 95 d.s1 = riff.readUnsignedShort(); 96 d.s2 = riff.readUnsignedShort(); 97 d.x1 = riff.readUnsignedByte(); 98 d.x2 = riff.readUnsignedByte(); 99 d.x3 = riff.readUnsignedByte(); 100 d.x4 = riff.readUnsignedByte(); 101 d.x5 = riff.readUnsignedByte(); 102 d.x6 = riff.readUnsignedByte(); 103 d.x7 = riff.readUnsignedByte(); 104 d.x8 = riff.readUnsignedByte(); 105 return d; 106 } 107 108 public int hashCode() { 109 return (int) i1; 110 } 111 112 public boolean equals(Object obj) { 113 if (!(obj instanceof GUID)) 114 return false; 115 GUID t = (GUID) obj; 116 if (i1 != t.i1) 117 return false; 118 if (s1 != t.s1) 119 return false; 120 if (s2 != t.s2) 121 return false; 122 if (x1 != t.x1) 123 return false; 124 if (x2 != t.x2) 125 return false; 126 if (x3 != t.x3) 127 return false; 128 if (x4 != t.x4) 129 return false; 130 if (x5 != t.x5) 131 return false; 132 if (x6 != t.x6) 133 return false; 134 if (x7 != t.x7) 135 return false; 136 if (x8 != t.x8) 137 return false; 138 return true; 139 } 140 141 } 142 143 private static final String[] channelnames = { "FL", "FR", "FC", "LF", 144 "BL", 145 "BR", // 5.1 146 "FLC", "FLR", "BC", "SL", "SR", "TC", "TFL", "TFC", "TFR", "TBL", 147 "TBC", "TBR" }; 148 149 private static final String[] allchannelnames = { "w1", "w2", "w3", "w4", "w5", 150 "w6", "w7", "w8", "w9", "w10", "w11", "w12", "w13", "w14", "w15", 151 "w16", "w17", "w18", "w19", "w20", "w21", "w22", "w23", "w24", 152 "w25", "w26", "w27", "w28", "w29", "w30", "w31", "w32", "w33", 153 "w34", "w35", "w36", "w37", "w38", "w39", "w40", "w41", "w42", 154 "w43", "w44", "w45", "w46", "w47", "w48", "w49", "w50", "w51", 155 "w52", "w53", "w54", "w55", "w56", "w57", "w58", "w59", "w60", 156 "w61", "w62", "w63", "w64" }; 157 158 private static final GUID SUBTYPE_PCM = new GUID(0x00000001, 0x0000, 0x0010, 159 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); 160 161 private static final GUID SUBTYPE_IEEE_FLOAT = new GUID(0x00000003, 0x0000, 162 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); 163 164 private String decodeChannelMask(long channelmask) { 165 StringBuilder sb = new StringBuilder(); 166 long m = 1; 167 for (int i = 0; i < allchannelnames.length; i++) { 168 if ((channelmask & m) != 0L) { 169 if (i < channelnames.length) { 170 sb.append(channelnames[i]).append(' '); 171 } else { 172 sb.append(allchannelnames[i]).append(' '); 173 } 174 } 175 m *= 2L; 176 } 177 if (sb.length() == 0) 178 return null; 179 return sb.substring(0, sb.length() - 1); 180 181 } 182 183 public AudioFileFormat getAudioFileFormat(InputStream stream) 184 throws UnsupportedAudioFileException, IOException { 185 186 stream.mark(200); 187 AudioFileFormat format; 188 try { 189 format = internal_getAudioFileFormat(stream); 190 } finally { 191 stream.reset(); 192 } 193 return format; 194 } 195 196 private AudioFileFormat internal_getAudioFileFormat(InputStream stream) 197 throws UnsupportedAudioFileException, IOException { 198 199 RIFFReader riffiterator = new RIFFReader(stream); 200 if (!riffiterator.getFormat().equals("RIFF")) 201 throw new UnsupportedAudioFileException(); 202 if (!riffiterator.getType().equals("WAVE")) 203 throw new UnsupportedAudioFileException(); 204 205 boolean fmt_found = false; 206 boolean data_found = false; 207 208 int channels = 1; 209 long samplerate = 1; 210 // long framerate = 1; 211 int framesize = 1; 212 int bits = 1; 213 int validBitsPerSample = 1; 214 long channelMask = 0; 215 GUID subFormat = null; 216 217 while (riffiterator.hasNextChunk()) { 218 RIFFReader chunk = riffiterator.nextChunk(); 219 220 if (chunk.getFormat().equals("fmt ")) { 221 fmt_found = true; 222 223 int format = chunk.readUnsignedShort(); 224 if (format != 0xFFFE) 225 throw new UnsupportedAudioFileException(); // WAVE_FORMAT_EXTENSIBLE 226 // only 227 channels = chunk.readUnsignedShort(); 228 samplerate = chunk.readUnsignedInt(); 229 /* framerate = */chunk.readUnsignedInt(); 230 framesize = chunk.readUnsignedShort(); 231 bits = chunk.readUnsignedShort(); 232 int cbSize = chunk.readUnsignedShort(); 233 if (cbSize != 22) 234 throw new UnsupportedAudioFileException(); 235 validBitsPerSample = chunk.readUnsignedShort(); 236 if (validBitsPerSample > bits) 237 throw new UnsupportedAudioFileException(); 238 channelMask = chunk.readUnsignedInt(); 239 subFormat = GUID.read(chunk); 240 241 } 242 if (chunk.getFormat().equals("data")) { 243 data_found = true; 244 break; 245 } 246 } 247 248 if (!fmt_found) 249 throw new UnsupportedAudioFileException(); 250 if (!data_found) 251 throw new UnsupportedAudioFileException(); 252 253 Map<String, Object> p = new HashMap<String, Object>(); 254 String s_channelmask = decodeChannelMask(channelMask); 255 if (s_channelmask != null) 256 p.put("channelOrder", s_channelmask); 257 if (channelMask != 0) 258 p.put("channelMask", channelMask); 259 // validBitsPerSample is only informational for PCM data, 260 // data is still encode according to SampleSizeInBits. 261 p.put("validBitsPerSample", validBitsPerSample); 262 263 AudioFormat audioformat = null; 264 if (subFormat.equals(SUBTYPE_PCM)) { 265 if (bits == 8) { 266 audioformat = new AudioFormat(Encoding.PCM_UNSIGNED, 267 samplerate, bits, channels, framesize, samplerate, 268 false, p); 269 } else { 270 audioformat = new AudioFormat(Encoding.PCM_SIGNED, samplerate, 271 bits, channels, framesize, samplerate, false, p); 272 } 273 } else if (subFormat.equals(SUBTYPE_IEEE_FLOAT)) { 274 audioformat = new AudioFormat(Encoding.PCM_FLOAT, 275 samplerate, bits, channels, framesize, samplerate, false, p); 276 } else 277 throw new UnsupportedAudioFileException(); 278 279 AudioFileFormat fileformat = new AudioFileFormat( 280 AudioFileFormat.Type.WAVE, audioformat, 281 AudioSystem.NOT_SPECIFIED); 282 return fileformat; 283 } 284 285 public AudioInputStream getAudioInputStream(InputStream stream) 286 throws UnsupportedAudioFileException, IOException { 287 288 AudioFileFormat format = getAudioFileFormat(stream); 289 RIFFReader riffiterator = new RIFFReader(stream); 290 if (!riffiterator.getFormat().equals("RIFF")) 291 throw new UnsupportedAudioFileException(); 292 if (!riffiterator.getType().equals("WAVE")) 293 throw new UnsupportedAudioFileException(); 294 while (riffiterator.hasNextChunk()) { 295 RIFFReader chunk = riffiterator.nextChunk(); 296 if (chunk.getFormat().equals("data")) { 297 return new AudioInputStream(chunk, format.getFormat(), chunk 298 .getSize()); 299 } 300 } 301 throw new UnsupportedAudioFileException(); 302 } 303 304 public AudioFileFormat getAudioFileFormat(URL url) 305 throws UnsupportedAudioFileException, IOException { 306 InputStream stream = url.openStream(); 307 AudioFileFormat format; 308 try { 309 format = getAudioFileFormat(new BufferedInputStream(stream)); 310 } finally { 311 stream.close(); 312 } 313 return format; 314 } 315 316 public AudioFileFormat getAudioFileFormat(File file) 317 throws UnsupportedAudioFileException, IOException { 318 InputStream stream = new FileInputStream(file); 319 AudioFileFormat format; 320 try { 321 format = getAudioFileFormat(new BufferedInputStream(stream)); 322 } finally { 323 stream.close(); 324 } 325 return format; 326 } 327 328 public AudioInputStream getAudioInputStream(URL url) 329 throws UnsupportedAudioFileException, IOException { 330 return getAudioInputStream(new BufferedInputStream(url.openStream())); 331 } 332 333 public AudioInputStream getAudioInputStream(File file) 334 throws UnsupportedAudioFileException, IOException { 335 return getAudioInputStream(new BufferedInputStream(new FileInputStream( 336 file))); 337 } 338 339 }