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 int validBitsPerSample = 1; 188 long channelMask = 0; 189 GUID subFormat = null; 190 191 while (riffiterator.hasNextChunk()) { 192 RIFFReader chunk = riffiterator.nextChunk(); 193 194 if (chunk.getFormat().equals("fmt ")) { 195 fmt_found = true; 196 197 int format = chunk.readUnsignedShort(); 198 if (format != WaveFileFormat.WAVE_FORMAT_EXTENSIBLE) { 199 throw new UnsupportedAudioFileException(); 200 } 201 channels = chunk.readUnsignedShort(); 202 samplerate = chunk.readUnsignedInt(); 203 /* framerate = */chunk.readUnsignedInt(); 204 framesize = chunk.readUnsignedShort(); 205 bits = chunk.readUnsignedShort(); 206 int cbSize = chunk.readUnsignedShort(); 207 if (cbSize != 22) 208 throw new UnsupportedAudioFileException(); 209 validBitsPerSample = chunk.readUnsignedShort(); 210 if (validBitsPerSample > bits) 211 throw new UnsupportedAudioFileException(); 212 channelMask = chunk.readUnsignedInt(); 213 subFormat = GUID.read(chunk); 214 215 } 216 if (chunk.getFormat().equals("data")) { 217 data_found = true; 218 break; 219 } 220 } 221 if (!fmt_found || !data_found) { 222 throw new UnsupportedAudioFileException(); 223 } 224 Map<String, Object> p = new HashMap<String, Object>(); 225 String s_channelmask = decodeChannelMask(channelMask); 226 if (s_channelmask != null) 227 p.put("channelOrder", s_channelmask); 228 if (channelMask != 0) 229 p.put("channelMask", channelMask); 230 // validBitsPerSample is only informational for PCM data, 231 // data is still encode according to SampleSizeInBits. 232 p.put("validBitsPerSample", validBitsPerSample); 233 234 AudioFormat audioformat = null; 235 if (subFormat.equals(SUBTYPE_PCM)) { 236 if (bits == 8) { 237 audioformat = new AudioFormat(Encoding.PCM_UNSIGNED, 238 samplerate, bits, channels, framesize, samplerate, 239 false, p); 240 } else { 241 audioformat = new AudioFormat(Encoding.PCM_SIGNED, samplerate, 242 bits, channels, framesize, samplerate, false, p); 243 } 244 } else if (subFormat.equals(SUBTYPE_IEEE_FLOAT)) { 245 audioformat = new AudioFormat(Encoding.PCM_FLOAT, 246 samplerate, bits, channels, framesize, samplerate, false, p); 247 } else { 248 throw new UnsupportedAudioFileException(); 249 } 250 return new AudioFileFormat(AudioFileFormat.Type.WAVE, audioformat, 251 AudioSystem.NOT_SPECIFIED); 252 } 253 254 @Override 255 public AudioInputStream getAudioInputStream(final InputStream stream) 256 throws UnsupportedAudioFileException, IOException { 257 258 final AudioFileFormat format = getAudioFileFormat(stream); 259 // we've got everything, the stream is supported and it is at the 260 // beginning of the header, so find the data chunk again and return an 261 // AudioInputStream 262 final RIFFReader riffiterator = new RIFFReader(stream); 263 while (riffiterator.hasNextChunk()) { 264 RIFFReader chunk = riffiterator.nextChunk(); 265 if (chunk.getFormat().equals("data")) { 266 final AudioFormat af = format.getFormat(); 267 final long length = chunk.getSize() / af.getFrameSize(); 268 return new AudioInputStream(chunk, af, length); 269 } 270 } 271 throw new UnsupportedAudioFileException(); 272 } 273 }