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 }