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