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 26package com.sun.media.sound; 27 28import java.io.IOException; 29import java.io.InputStream; 30import java.util.HashMap; 31import java.util.Map; 32 33import javax.sound.sampled.AudioFileFormat; 34import javax.sound.sampled.AudioFormat; 35import javax.sound.sampled.AudioFormat.Encoding; 36import javax.sound.sampled.AudioInputStream; 37import javax.sound.sampled.UnsupportedAudioFileException; 38 39/** 40 * WAVE file reader for files using format WAVE_FORMAT_EXTENSIBLE (0xFFFE). 41 * 42 * @author Karl Helgason 43 */ 44public final class WaveExtensibleFileReader extends SunFileReader { 45 46 private static final class GUID { 47 private long i1; 48 private int s1; 49 private int s2; 50 private int x1; 51 private int x2; 52 private int x3; 53 private int x4; 54 private int x5; 55 private int x6; 56 private int x7; 57 private int x8; 58 private GUID() { 59 } 60 61 GUID(long i1, int s1, int s2, int x1, int x2, int x3, int x4, 62 int x5, int x6, int x7, int x8) { 63 this.i1 = i1; 64 this.s1 = s1; 65 this.s2 = s2; 66 this.x1 = x1; 67 this.x2 = x2; 68 this.x3 = x3; 69 this.x4 = x4; 70 this.x5 = x5; 71 this.x6 = x6; 72 this.x7 = x7; 73 this.x8 = x8; 74 } 75 76 public static GUID read(RIFFReader riff) throws IOException { 77 GUID d = new GUID(); 78 d.i1 = riff.readUnsignedInt(); 79 d.s1 = riff.readUnsignedShort(); 80 d.s2 = riff.readUnsignedShort(); 81 d.x1 = riff.readUnsignedByte(); 82 d.x2 = riff.readUnsignedByte(); 83 d.x3 = riff.readUnsignedByte(); 84 d.x4 = riff.readUnsignedByte(); 85 d.x5 = riff.readUnsignedByte(); 86 d.x6 = riff.readUnsignedByte(); 87 d.x7 = riff.readUnsignedByte(); 88 d.x8 = riff.readUnsignedByte(); 89 return d; 90 } 91 92 @Override 93 public int hashCode() { 94 return (int) i1; 95 } 96 97 @Override 98 public boolean equals(Object obj) { 99 if (!(obj instanceof GUID)) 100 return false; 101 GUID t = (GUID) obj; 102 if (i1 != t.i1) 103 return false; 104 if (s1 != t.s1) 105 return false; 106 if (s2 != t.s2) 107 return false; 108 if (x1 != t.x1) 109 return false; 110 if (x2 != t.x2) 111 return false; 112 if (x3 != t.x3) 113 return false; 114 if (x4 != t.x4) 115 return false; 116 if (x5 != t.x5) 117 return false; 118 if (x6 != t.x6) 119 return false; 120 if (x7 != t.x7) 121 return false; 122 if (x8 != t.x8) 123 return false; 124 return true; 125 } 126 } 127 128 private static final String[] channelnames = { "FL", "FR", "FC", "LF", 129 "BL", 130 "BR", // 5.1 131 "FLC", "FLR", "BC", "SL", "SR", "TC", "TFL", "TFC", "TFR", "TBL", 132 "TBC", "TBR" }; 133 134 private static final String[] allchannelnames = { "w1", "w2", "w3", "w4", "w5", 135 "w6", "w7", "w8", "w9", "w10", "w11", "w12", "w13", "w14", "w15", 136 "w16", "w17", "w18", "w19", "w20", "w21", "w22", "w23", "w24", 137 "w25", "w26", "w27", "w28", "w29", "w30", "w31", "w32", "w33", 138 "w34", "w35", "w36", "w37", "w38", "w39", "w40", "w41", "w42", 139 "w43", "w44", "w45", "w46", "w47", "w48", "w49", "w50", "w51", 140 "w52", "w53", "w54", "w55", "w56", "w57", "w58", "w59", "w60", 141 "w61", "w62", "w63", "w64" }; 142 143 private static final GUID SUBTYPE_PCM = new GUID(0x00000001, 0x0000, 0x0010, 144 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); 145 146 private static final GUID SUBTYPE_IEEE_FLOAT = new GUID(0x00000003, 0x0000, 147 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); 148 149 private static String decodeChannelMask(long channelmask) { 150 StringBuilder sb = new StringBuilder(); 151 long m = 1; 152 for (int i = 0; i < allchannelnames.length; i++) { 153 if ((channelmask & m) != 0L) { 154 if (i < channelnames.length) { 155 sb.append(channelnames[i]).append(' '); 156 } else { 157 sb.append(allchannelnames[i]).append(' '); 158 } 159 } 160 m *= 2L; 161 } 162 if (sb.length() == 0) 163 return null; 164 return sb.substring(0, sb.length() - 1); 165 166 } 167 168 @Override 169 StandardFileFormat getAudioFileFormatImpl(final InputStream stream) 170 throws UnsupportedAudioFileException, IOException { 171 172 RIFFReader riffiterator = new RIFFReader(stream); 173 if (!riffiterator.getFormat().equals("RIFF")) 174 throw new UnsupportedAudioFileException(); 175 if (!riffiterator.getType().equals("WAVE")) 176 throw new UnsupportedAudioFileException(); 177 178 boolean fmt_found = false; 179 boolean data_found = false; 180 181 int channels = 1; 182 long samplerate = 1; 183 // long framerate = 1; 184 int framesize = 1; 185 int bits = 1; 186 long dataSize = 0; 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 dataSize = chunk.getSize(); 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<>(); 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 StandardFileFormat(AudioFileFormat.Type.WAVE, audioformat, 252 dataSize / audioformat.getFrameSize()); 253 } 254 255 @Override 256 public AudioInputStream getAudioInputStream(final InputStream stream) 257 throws UnsupportedAudioFileException, IOException { 258 259 final StandardFileFormat format = getAudioFileFormat(stream); 260 final AudioFormat af = format.getFormat(); 261 final long length = format.getLongFrameLength(); 262 // we've got everything, the stream is supported and it is at the 263 // beginning of the header, so find the data chunk again and return an 264 // AudioInputStream 265 final RIFFReader riffiterator = new RIFFReader(stream); 266 while (riffiterator.hasNextChunk()) { 267 RIFFReader chunk = riffiterator.nextChunk(); 268 if (chunk.getFormat().equals("data")) { 269 return new AudioInputStream(chunk, af, length); 270 } 271 } 272 throw new UnsupportedAudioFileException(); 273 } 274} 275