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 }