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.UnsupportedAudioFileException;
  38 
  39 /**
  40  * WAVE file reader for files using format WAVE_FORMAT_EXTENSIBLE (0xFFFE).
  41  *
  42  * @author Karl Helgason
  43  */
  44 public final class WaveExtensibleFileReader extends SunFileReader {
  45 
  46     private static 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<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 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 }