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.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 130 private static final String[] channelnames = { "FL", "FR", "FC", "LF", 131 "BL", 132 "BR", // 5.1 133 "FLC", "FLR", "BC", "SL", "SR", "TC", "TFL", "TFC", "TFR", "TBL", 134 "TBC", "TBR" }; 135 136 private static final String[] allchannelnames = { "w1", "w2", "w3", "w4", "w5", 137 "w6", "w7", "w8", "w9", "w10", "w11", "w12", "w13", "w14", "w15", 138 "w16", "w17", "w18", "w19", "w20", "w21", "w22", "w23", "w24", 139 "w25", "w26", "w27", "w28", "w29", "w30", "w31", "w32", "w33", 140 "w34", "w35", "w36", "w37", "w38", "w39", "w40", "w41", "w42", 141 "w43", "w44", "w45", "w46", "w47", "w48", "w49", "w50", "w51", 142 "w52", "w53", "w54", "w55", "w56", "w57", "w58", "w59", "w60", 143 "w61", "w62", "w63", "w64" }; 144 145 private static final GUID SUBTYPE_PCM = new GUID(0x00000001, 0x0000, 0x0010, 146 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); 147 148 private static final GUID SUBTYPE_IEEE_FLOAT = new GUID(0x00000003, 0x0000, 149 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); 150 151 private static String decodeChannelMask(long channelmask) { 152 StringBuilder sb = new StringBuilder(); 153 long m = 1; 154 for (int i = 0; i < allchannelnames.length; i++) { 155 if ((channelmask & m) != 0L) { 156 if (i < channelnames.length) { 157 sb.append(channelnames[i]).append(' '); 158 } else { 159 sb.append(allchannelnames[i]).append(' '); 160 } 161 } 162 m *= 2L; 163 } 164 if (sb.length() == 0) 165 return null; 166 return sb.substring(0, sb.length() - 1); 167 168 } 169 170 @Override 171 AudioFileFormat getAudioFileFormatImpl(final InputStream stream) 172 throws UnsupportedAudioFileException, IOException { 173 174 RIFFReader riffiterator = new RIFFReader(stream); 175 if (!riffiterator.getFormat().equals("RIFF")) 176 throw new UnsupportedAudioFileException(); 177 if (!riffiterator.getType().equals("WAVE")) 178 throw new UnsupportedAudioFileException(); 179 180 boolean fmt_found = false; 181 boolean data_found = false; 182 183 int channels = 1; 184 long samplerate = 1; 185 // long framerate = 1; 186 int framesize = 1; 187 int bits = 1; 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 != 0xFFFE) 200 throw new UnsupportedAudioFileException(); // WAVE_FORMAT_EXTENSIBLE 201 // only 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 data_found = true; 219 break; 220 } 221 } 222 if (!fmt_found || !data_found) { 223 throw new UnsupportedAudioFileException(); 224 } 225 Map<String, Object> p = new HashMap<String, Object>(); 226 String s_channelmask = decodeChannelMask(channelMask); 227 if (s_channelmask != null) 228 p.put("channelOrder", s_channelmask); 229 if (channelMask != 0) 230 p.put("channelMask", channelMask); 231 // validBitsPerSample is only informational for PCM data, 232 // data is still encode according to SampleSizeInBits. 233 p.put("validBitsPerSample", validBitsPerSample); 234 235 AudioFormat audioformat = null; 236 if (subFormat.equals(SUBTYPE_PCM)) { 237 if (bits == 8) { 238 audioformat = new AudioFormat(Encoding.PCM_UNSIGNED, 239 samplerate, bits, channels, framesize, samplerate, 240 false, p); 241 } else { 242 audioformat = new AudioFormat(Encoding.PCM_SIGNED, samplerate, 243 bits, channels, framesize, samplerate, false, p); 244 } 245 } else if (subFormat.equals(SUBTYPE_IEEE_FLOAT)) { 246 audioformat = new AudioFormat(Encoding.PCM_FLOAT, 247 samplerate, bits, channels, framesize, samplerate, false, p); 248 } else { 249 throw new UnsupportedAudioFileException(); 250 } 251 return new AudioFileFormat(AudioFileFormat.Type.WAVE, audioformat, 252 AudioSystem.NOT_SPECIFIED); 253 } 254 255 @Override 256 public AudioInputStream getAudioInputStream(final InputStream stream) 257 throws UnsupportedAudioFileException, IOException { 258 259 AudioFileFormat format = getAudioFileFormat(stream); 260 // we've got everything, the stream is supported and it is at the 261 // beginning of the header, so find the data chunk again and return an 262 // AudioInputStream 263 RIFFReader riffiterator = new RIFFReader(stream); 264 while (riffiterator.hasNextChunk()) { 265 RIFFReader chunk = riffiterator.nextChunk(); 266 if (chunk.getFormat().equals("data")) { 267 return new AudioInputStream(chunk, format.getFormat(), chunk 268 .getSize()); 269 } 270 } 271 throw new UnsupportedAudioFileException(); 272 } 273 }