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