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         long dataSize = 0;
 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 != WaveFileFormat.WAVE_FORMAT_EXTENSIBLE) {
 200                     throw new UnsupportedAudioFileException();
 201                 }
 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                 dataSize = chunk.getSize();
 219                 data_found = true;
 220                 break;
 221             }
 222         }
 223         if (!fmt_found || !data_found) {
 224             throw new UnsupportedAudioFileException();
 225         }
 226         Map<String, Object> p = new HashMap<String, Object>();
 227         String s_channelmask = decodeChannelMask(channelMask);
 228         if (s_channelmask != null)
 229             p.put("channelOrder", s_channelmask);
 230         if (channelMask != 0)
 231             p.put("channelMask", channelMask);
 232         // validBitsPerSample is only informational for PCM data,
 233         // data is still encode according to SampleSizeInBits.
 234         p.put("validBitsPerSample", validBitsPerSample);
 235 
 236         AudioFormat audioformat = null;
 237         if (subFormat.equals(SUBTYPE_PCM)) {
 238             if (bits == 8) {
 239                 audioformat = new AudioFormat(Encoding.PCM_UNSIGNED,
 240                         samplerate, bits, channels, framesize, samplerate,
 241                         false, p);
 242             } else {
 243                 audioformat = new AudioFormat(Encoding.PCM_SIGNED, samplerate,
 244                         bits, channels, framesize, samplerate, false, p);
 245             }
 246         } else if (subFormat.equals(SUBTYPE_IEEE_FLOAT)) {
 247             audioformat = new AudioFormat(Encoding.PCM_FLOAT,
 248                     samplerate, bits, channels, framesize, samplerate, false, p);
 249         } else {
 250             throw new UnsupportedAudioFileException();
 251         }
 252         long frameLength = dataSize / audioformat.getFrameSize();
 253         if (frameLength > Integer.MAX_VALUE) {
 254             frameLength = AudioSystem.NOT_SPECIFIED;
 255         }
 256         return new AudioFileFormat(AudioFileFormat.Type.WAVE, audioformat,
 257                                    (int) frameLength);
 258     }
 259 
 260     @Override
 261     public AudioInputStream getAudioInputStream(final InputStream stream)
 262             throws UnsupportedAudioFileException, IOException {
 263 
 264         final AudioFileFormat format = getAudioFileFormat(stream);
 265         // we've got everything, the stream is supported and it is at the
 266         // beginning of the header, so find the data chunk again and return an
 267         // AudioInputStream
 268         final RIFFReader riffiterator = new RIFFReader(stream);
 269         while (riffiterator.hasNextChunk()) {
 270             RIFFReader chunk = riffiterator.nextChunk();
 271             if (chunk.getFormat().equals("data")) {
 272                 final AudioFormat af = format.getFormat();
 273                 final long length = chunk.getSize() / af.getFrameSize();
 274                 return new AudioInputStream(chunk, af, length);
 275             }
 276         }
 277         throw new UnsupportedAudioFileException();
 278     }
 279 }