1 /*
   2  * Copyright (c) 2002, 2014, 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.util.Vector;
  29 
  30 import javax.sound.sampled.Control;
  31 import javax.sound.sampled.Line;
  32 import javax.sound.sampled.LineUnavailableException;
  33 import javax.sound.sampled.Port;
  34 import javax.sound.sampled.BooleanControl;
  35 import javax.sound.sampled.CompoundControl;
  36 import javax.sound.sampled.FloatControl;
  37 
  38 
  39 /**
  40  * A Mixer which only provides Ports.
  41  *
  42  * @author Florian Bomers
  43  */
  44 final class PortMixer extends AbstractMixer {
  45 
  46     // CONSTANTS
  47     private static final int SRC_UNKNOWN      = 0x01;
  48     private static final int SRC_MICROPHONE   = 0x02;
  49     private static final int SRC_LINE_IN      = 0x03;
  50     private static final int SRC_COMPACT_DISC = 0x04;
  51     private static final int SRC_MASK         = 0xFF;
  52 
  53     private static final int DST_UNKNOWN      = 0x0100;
  54     private static final int DST_SPEAKER      = 0x0200;
  55     private static final int DST_HEADPHONE    = 0x0300;
  56     private static final int DST_LINE_OUT     = 0x0400;
  57     private static final int DST_MASK         = 0xFF00;
  58 
  59     // INSTANCE VARIABLES
  60     private Port.Info[] portInfos;
  61     // cache of instantiated ports
  62     private PortMixerPort[] ports;
  63 
  64     // instance ID of the native implementation
  65     private long id = 0;
  66 
  67     // CONSTRUCTOR
  68     PortMixer(PortMixerProvider.PortMixerInfo portMixerInfo) {
  69         // pass in Line.Info, mixer, controls
  70         super(portMixerInfo,              // Mixer.Info
  71               null,                       // Control[]
  72               null,                       // Line.Info[] sourceLineInfo
  73               null);                      // Line.Info[] targetLineInfo
  74 
  75         if (Printer.trace) Printer.trace(">> PortMixer: constructor");
  76 
  77         int count = 0;
  78         int srcLineCount = 0;
  79         int dstLineCount = 0;
  80 
  81         try {
  82             try {
  83                 id = nOpen(getMixerIndex());
  84                 if (id != 0) {
  85                     count = nGetPortCount(id);
  86                     if (count < 0) {
  87                         if (Printer.trace) Printer.trace("nGetPortCount() returned error code: " + count);
  88                         count = 0;
  89                     }
  90                 }
  91             } catch (Exception e) {}
  92 
  93             portInfos = new Port.Info[count];
  94 
  95             for (int i = 0; i < count; i++) {
  96                 int type = nGetPortType(id, i);
  97                 srcLineCount += ((type & SRC_MASK) != 0)?1:0;
  98                 dstLineCount += ((type & DST_MASK) != 0)?1:0;
  99                 portInfos[i] = getPortInfo(i, type);
 100             }
 101         } finally {
 102             if (id != 0) {
 103                 nClose(id);
 104             }
 105             id = 0;
 106         }
 107 
 108         // fill sourceLineInfo and targetLineInfos with copies of the ones in portInfos
 109         sourceLineInfo = new Port.Info[srcLineCount];
 110         targetLineInfo = new Port.Info[dstLineCount];
 111 
 112         srcLineCount = 0; dstLineCount = 0;
 113         for (int i = 0; i < count; i++) {
 114             if (portInfos[i].isSource()) {
 115                 sourceLineInfo[srcLineCount++] = portInfos[i];
 116             } else {
 117                 targetLineInfo[dstLineCount++] = portInfos[i];
 118             }
 119         }
 120 
 121         if (Printer.trace) Printer.trace("<< PortMixer: constructor completed");
 122     }
 123 
 124 
 125     // ABSTRACT MIXER: ABSTRACT METHOD IMPLEMENTATIONS
 126 
 127     public Line getLine(Line.Info info) throws LineUnavailableException {
 128         Line.Info fullInfo = getLineInfo(info);
 129 
 130         if ((fullInfo != null) && (fullInfo instanceof Port.Info)) {
 131             for (int i = 0; i < portInfos.length; i++) {
 132                 if (fullInfo.equals(portInfos[i])) {
 133                     return getPort(i);
 134                 }
 135             }
 136         }
 137         throw new IllegalArgumentException("Line unsupported: " + info);
 138     }
 139 
 140 
 141     public int getMaxLines(Line.Info info) {
 142         Line.Info fullInfo = getLineInfo(info);
 143 
 144         // if it's not supported at all, return 0.
 145         if (fullInfo == null) {
 146             return 0;
 147         }
 148 
 149         if (fullInfo instanceof Port.Info) {
 150             //return AudioSystem.NOT_SPECIFIED; // if several instances of PortMixerPort
 151             return 1;
 152         }
 153         return 0;
 154     }
 155 
 156 
 157     protected void implOpen() throws LineUnavailableException {
 158         if (Printer.trace) Printer.trace(">> PortMixer: implOpen (id="+id+")");
 159 
 160         // open the mixer device
 161         id = nOpen(getMixerIndex());
 162 
 163         if (Printer.trace) Printer.trace("<< PortMixer: implOpen succeeded.");
 164     }
 165 
 166     protected void implClose() {
 167         if (Printer.trace) Printer.trace(">> PortMixer: implClose");
 168 
 169         // close the mixer device
 170         long thisID = id;
 171         id = 0;
 172         nClose(thisID);
 173         if (ports != null) {
 174             for (int i = 0; i < ports.length; i++) {
 175                 if (ports[i] != null) {
 176                     ports[i].disposeControls();
 177                 }
 178             }
 179         }
 180 
 181         if (Printer.trace) Printer.trace("<< PortMixer: implClose succeeded");
 182     }
 183 
 184     protected void implStart() {}
 185     protected void implStop() {}
 186 
 187     // IMPLEMENTATION HELPERS
 188 
 189     private Port.Info getPortInfo(int portIndex, int type) {
 190         switch (type) {
 191         case SRC_UNKNOWN:      return new PortInfo(nGetPortName(getID(), portIndex), true);
 192         case SRC_MICROPHONE:   return Port.Info.MICROPHONE;
 193         case SRC_LINE_IN:      return Port.Info.LINE_IN;
 194         case SRC_COMPACT_DISC: return Port.Info.COMPACT_DISC;
 195 
 196         case DST_UNKNOWN:      return new PortInfo(nGetPortName(getID(), portIndex), false);
 197         case DST_SPEAKER:      return Port.Info.SPEAKER;
 198         case DST_HEADPHONE:    return Port.Info.HEADPHONE;
 199         case DST_LINE_OUT:     return Port.Info.LINE_OUT;
 200         }
 201         // should never happen...
 202         if (Printer.debug) Printer.debug("unknown port type: "+type);
 203         return null;
 204     }
 205 
 206     int getMixerIndex() {
 207         return ((PortMixerProvider.PortMixerInfo) getMixerInfo()).getIndex();
 208     }
 209 
 210     Port getPort(int index) {
 211         if (ports == null) {
 212             ports = new PortMixerPort[portInfos.length];
 213         }
 214         if (ports[index] == null) {
 215             ports[index] = new PortMixerPort(portInfos[index], this, index);
 216             return ports[index];
 217         }
 218         // $$fb TODO: return (Port) (ports[index].clone());
 219         return ports[index];
 220     }
 221 
 222     long getID() {
 223         return id;
 224     }
 225 
 226     // INNER CLASSES
 227 
 228     /**
 229      * Private inner class representing a Port for the PortMixer.
 230      */
 231     private static final class PortMixerPort extends AbstractLine
 232             implements Port {
 233 
 234         private final int portIndex;
 235         private long id;
 236 
 237         // CONSTRUCTOR
 238         private PortMixerPort(Port.Info info,
 239                               PortMixer mixer,
 240                               int portIndex) {
 241             super(info, mixer, null);
 242             if (Printer.trace) Printer.trace("PortMixerPort CONSTRUCTOR: info: " + info);
 243             this.portIndex = portIndex;
 244         }
 245 
 246 
 247         // ABSTRACT METHOD IMPLEMENTATIONS
 248 
 249         // ABSTRACT LINE
 250 
 251         void implOpen() throws LineUnavailableException {
 252             if (Printer.trace) Printer.trace(">> PortMixerPort: implOpen().");
 253             long newID = ((PortMixer) mixer).getID();
 254             if ((id == 0) || (newID != id) || (controls.length == 0)) {
 255                 id = newID;
 256                 Vector<Control> vector = new Vector<>();
 257                 synchronized (vector) {
 258                     nGetControls(id, portIndex, vector);
 259                     controls = new Control[vector.size()];
 260                     for (int i = 0; i < controls.length; i++) {
 261                         controls[i] = vector.elementAt(i);
 262                     }
 263                 }
 264             } else {
 265                 enableControls(controls, true);
 266             }
 267             if (Printer.trace) Printer.trace("<< PortMixerPort: implOpen() succeeded");
 268         }
 269 
 270         private void enableControls(Control[] controls, boolean enable) {
 271             for (int i = 0; i < controls.length; i++) {
 272                 if (controls[i] instanceof BoolCtrl) {
 273                     ((BoolCtrl) controls[i]).closed = !enable;
 274                 }
 275                 else if (controls[i] instanceof FloatCtrl) {
 276                     ((FloatCtrl) controls[i]).closed = !enable;
 277                 }
 278                 else if (controls[i] instanceof CompoundControl) {
 279                     enableControls(((CompoundControl) controls[i]).getMemberControls(), enable);
 280                 }
 281             }
 282         }
 283 
 284         private void disposeControls() {
 285             enableControls(controls, false);
 286             controls = new Control[0];
 287         }
 288 
 289 
 290         void implClose() {
 291             if (Printer.trace) Printer.trace(">> PortMixerPort: implClose()");
 292             // get rid of controls
 293             enableControls(controls, false);
 294             if (Printer.trace) Printer.trace("<< PortMixerPort: implClose() succeeded");
 295         }
 296 
 297         // METHOD OVERRIDES
 298 
 299         // this is very similar to open(AudioFormat, int) in AbstractDataLine...
 300         public void open() throws LineUnavailableException {
 301             synchronized (mixer) {
 302                 // if the line is not currently open, try to open it with this format and buffer size
 303                 if (!isOpen()) {
 304                     if (Printer.trace) Printer.trace("> PortMixerPort: open");
 305                     // reserve mixer resources for this line
 306                     mixer.open(this);
 307                     try {
 308                         // open the line.  may throw LineUnavailableException.
 309                         implOpen();
 310 
 311                         // if we succeeded, set the open state to true and send events
 312                         setOpen(true);
 313                     } catch (LineUnavailableException e) {
 314                         // release mixer resources for this line and then throw the exception
 315                         mixer.close(this);
 316                         throw e;
 317                     }
 318                     if (Printer.trace) Printer.trace("< PortMixerPort: open succeeded");
 319                 }
 320             }
 321         }
 322 
 323         // this is very similar to close() in AbstractDataLine...
 324         public void close() {
 325             synchronized (mixer) {
 326                 if (isOpen()) {
 327                     if (Printer.trace) Printer.trace("> PortMixerPort.close()");
 328 
 329                     // set the open state to false and send events
 330                     setOpen(false);
 331 
 332                     // close resources for this line
 333                     implClose();
 334 
 335                     // release mixer resources for this line
 336                     mixer.close(this);
 337                     if (Printer.trace) Printer.trace("< PortMixerPort.close() succeeded");
 338                 }
 339             }
 340         }
 341 
 342     } // class PortMixerPort
 343 
 344     /**
 345      * Private inner class representing a BooleanControl for PortMixerPort
 346      */
 347     private static final class BoolCtrl extends BooleanControl {
 348         // the handle to the native control function
 349         private final long controlID;
 350         private boolean closed = false;
 351 
 352         private static BooleanControl.Type createType(String name) {
 353             if (name.equals("Mute")) {
 354                 return BooleanControl.Type.MUTE;
 355             }
 356             else if (name.equals("Select")) {
 357                 // $$fb add as new static type?
 358                 //return BooleanControl.Type.SELECT;
 359             }
 360             return new BCT(name);
 361         }
 362 
 363 
 364         private BoolCtrl(long controlID, String name) {
 365             this(controlID, createType(name));
 366         }
 367 
 368         private BoolCtrl(long controlID, BooleanControl.Type typ) {
 369             super(typ, false);
 370             this.controlID = controlID;
 371         }
 372 
 373         public void setValue(boolean value) {
 374             if (!closed) {
 375                 nControlSetIntValue(controlID, value?1:0);
 376             }
 377         }
 378 
 379         public boolean getValue() {
 380             if (!closed) {
 381                 // never use any cached values
 382                 return (nControlGetIntValue(controlID)!=0)?true:false;
 383             }
 384             // ??
 385             return false;
 386         }
 387 
 388         /**
 389          * inner class for custom types
 390          */
 391         private static final class BCT extends BooleanControl.Type {
 392             private BCT(String name) {
 393                 super(name);
 394             }
 395         }
 396     }
 397 
 398     /**
 399      * Private inner class representing a CompoundControl for PortMixerPort
 400      */
 401     private static final class CompCtrl extends CompoundControl {
 402         private CompCtrl(String name, Control[] controls) {
 403             super(new CCT(name), controls);
 404         }
 405 
 406         /**
 407          * inner class for custom compound control types
 408          */
 409         private static final class CCT extends CompoundControl.Type {
 410             private CCT(String name) {
 411                 super(name);
 412             }
 413         }
 414     }
 415 
 416     /**
 417      * Private inner class representing a BooleanControl for PortMixerPort
 418      */
 419     private static final class FloatCtrl extends FloatControl {
 420         // the handle to the native control function
 421         private final long controlID;
 422         private boolean closed = false;
 423 
 424         // predefined float control types. See also Ports.h
 425         private final static FloatControl.Type[] FLOAT_CONTROL_TYPES = {
 426             null,
 427             FloatControl.Type.BALANCE,
 428             FloatControl.Type.MASTER_GAIN,
 429             FloatControl.Type.PAN,
 430             FloatControl.Type.VOLUME
 431         };
 432 
 433         private FloatCtrl(long controlID, String name,
 434                           float min, float max, float precision, String units) {
 435             this(controlID, new FCT(name), min, max, precision, units);
 436         }
 437 
 438         private FloatCtrl(long controlID, int type,
 439                           float min, float max, float precision, String units) {
 440             this(controlID, FLOAT_CONTROL_TYPES[type], min, max, precision, units);
 441         }
 442 
 443         private FloatCtrl(long controlID, FloatControl.Type typ,
 444                          float min, float max, float precision, String units) {
 445             super(typ, min, max, precision, 1000, min, units);
 446             this.controlID = controlID;
 447         }
 448 
 449         public void setValue(float value) {
 450             if (!closed) {
 451                 nControlSetFloatValue(controlID, value);
 452             }
 453         }
 454 
 455         public float getValue() {
 456             if (!closed) {
 457                 // never use any cached values
 458                 return nControlGetFloatValue(controlID);
 459             }
 460             // ??
 461             return getMinimum();
 462         }
 463 
 464         /**
 465          * inner class for custom types
 466          */
 467         private static final class FCT extends FloatControl.Type {
 468             private FCT(String name) {
 469                 super(name);
 470             }
 471         }
 472     }
 473 
 474     /**
 475      * Private inner class representing a port info
 476      */
 477     private static final class PortInfo extends Port.Info {
 478         private PortInfo(String name, boolean isSource) {
 479             super(Port.class, name, isSource);
 480         }
 481     }
 482 
 483     // open the mixer with the given index. Returns a handle ID
 484     private static native long nOpen(int mixerIndex) throws LineUnavailableException;
 485     private static native void nClose(long id);
 486 
 487     // gets the number of ports for this mixer
 488     private static native int nGetPortCount(long id);
 489 
 490     // gets the type of the port with this index
 491     private static native int nGetPortType(long id, int portIndex);
 492 
 493     // gets the name of the port with this index
 494     private static native String nGetPortName(long id, int portIndex);
 495 
 496     // fills the vector with the controls for this port
 497     @SuppressWarnings("rawtypes")
 498     private static native void nGetControls(long id, int portIndex, Vector vector);
 499 
 500     // getters/setters for controls
 501     private static native void nControlSetIntValue(long controlID, int value);
 502     private static native int nControlGetIntValue(long controlID);
 503     private static native void nControlSetFloatValue(long controlID, float value);
 504     private static native float nControlGetFloatValue(long controlID);
 505 
 506 }