/*
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.media.sound;
import java.util.Vector;
import javax.sound.sampled.Control;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Port;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.CompoundControl;
import javax.sound.sampled.FloatControl;
/**
* A Mixer which only provides Ports.
*
* @author Florian Bomers
*/
final class PortMixer extends AbstractMixer {
// CONSTANTS
private static final int SRC_UNKNOWN = 0x01;
private static final int SRC_MICROPHONE = 0x02;
private static final int SRC_LINE_IN = 0x03;
private static final int SRC_COMPACT_DISC = 0x04;
private static final int SRC_MASK = 0xFF;
private static final int DST_UNKNOWN = 0x0100;
private static final int DST_SPEAKER = 0x0200;
private static final int DST_HEADPHONE = 0x0300;
private static final int DST_LINE_OUT = 0x0400;
private static final int DST_MASK = 0xFF00;
// INSTANCE VARIABLES
private Port.Info[] portInfos;
// cache of instantiated ports
private PortMixerPort[] ports;
// instance ID of the native implementation
private long id = 0;
// CONSTRUCTOR
PortMixer(PortMixerProvider.PortMixerInfo portMixerInfo) {
// pass in Line.Info, mixer, controls
super(portMixerInfo, // Mixer.Info
null, // Control[]
null, // Line.Info[] sourceLineInfo
null); // Line.Info[] targetLineInfo
if (Printer.trace) Printer.trace(">> PortMixer: constructor");
int count = 0;
int srcLineCount = 0;
int dstLineCount = 0;
try {
try {
id = nOpen(getMixerIndex());
if (id != 0) {
count = nGetPortCount(id);
if (count < 0) {
if (Printer.trace) Printer.trace("nGetPortCount() returned error code: " + count);
count = 0;
}
}
} catch (Exception e) {}
portInfos = new Port.Info[count];
for (int i = 0; i < count; i++) {
int type = nGetPortType(id, i);
srcLineCount += ((type & SRC_MASK) != 0)?1:0;
dstLineCount += ((type & DST_MASK) != 0)?1:0;
portInfos[i] = getPortInfo(i, type);
}
} finally {
if (id != 0) {
nClose(id);
}
id = 0;
}
// fill sourceLineInfo and targetLineInfos with copies of the ones in portInfos
sourceLineInfo = new Port.Info[srcLineCount];
targetLineInfo = new Port.Info[dstLineCount];
srcLineCount = 0; dstLineCount = 0;
for (int i = 0; i < count; i++) {
if (portInfos[i].isSource()) {
sourceLineInfo[srcLineCount++] = portInfos[i];
} else {
targetLineInfo[dstLineCount++] = portInfos[i];
}
}
if (Printer.trace) Printer.trace("<< PortMixer: constructor completed");
}
// ABSTRACT MIXER: ABSTRACT METHOD IMPLEMENTATIONS
public Line getLine(Line.Info info) throws LineUnavailableException {
Line.Info fullInfo = getLineInfo(info);
if ((fullInfo != null) && (fullInfo instanceof Port.Info)) {
for (int i = 0; i < portInfos.length; i++) {
if (fullInfo.equals(portInfos[i])) {
return getPort(i);
}
}
}
throw new IllegalArgumentException("Line unsupported: " + info);
}
public int getMaxLines(Line.Info info) {
Line.Info fullInfo = getLineInfo(info);
// if it's not supported at all, return 0.
if (fullInfo == null) {
return 0;
}
if (fullInfo instanceof Port.Info) {
//return AudioSystem.NOT_SPECIFIED; // if several instances of PortMixerPort
return 1;
}
return 0;
}
protected void implOpen() throws LineUnavailableException {
if (Printer.trace) Printer.trace(">> PortMixer: implOpen (id="+id+")");
// open the mixer device
id = nOpen(getMixerIndex());
if (Printer.trace) Printer.trace("<< PortMixer: implOpen succeeded.");
}
protected void implClose() {
if (Printer.trace) Printer.trace(">> PortMixer: implClose");
// close the mixer device
long thisID = id;
id = 0;
nClose(thisID);
if (ports != null) {
for (int i = 0; i < ports.length; i++) {
if (ports[i] != null) {
ports[i].disposeControls();
}
}
}
if (Printer.trace) Printer.trace("<< PortMixer: implClose succeeded");
}
protected void implStart() {}
protected void implStop() {}
// IMPLEMENTATION HELPERS
private Port.Info getPortInfo(int portIndex, int type) {
switch (type) {
case SRC_UNKNOWN: return new PortInfo(nGetPortName(getID(), portIndex), true);
case SRC_MICROPHONE: return Port.Info.MICROPHONE;
case SRC_LINE_IN: return Port.Info.LINE_IN;
case SRC_COMPACT_DISC: return Port.Info.COMPACT_DISC;
case DST_UNKNOWN: return new PortInfo(nGetPortName(getID(), portIndex), false);
case DST_SPEAKER: return Port.Info.SPEAKER;
case DST_HEADPHONE: return Port.Info.HEADPHONE;
case DST_LINE_OUT: return Port.Info.LINE_OUT;
}
// should never happen...
if (Printer.debug) Printer.debug("unknown port type: "+type);
return null;
}
int getMixerIndex() {
return ((PortMixerProvider.PortMixerInfo) getMixerInfo()).getIndex();
}
Port getPort(int index) {
if (ports == null) {
ports = new PortMixerPort[portInfos.length];
}
if (ports[index] == null) {
ports[index] = new PortMixerPort((Port.Info)portInfos[index], this, index);
return ports[index];
}
// $$fb TODO: return (Port) (ports[index].clone());
return ports[index];
}
long getID() {
return id;
}
// INNER CLASSES
/**
* Private inner class representing a Port for the PortMixer.
*/
private static final class PortMixerPort extends AbstractLine
implements Port {
private final int portIndex;
private long id;
// CONSTRUCTOR
private PortMixerPort(Port.Info info,
PortMixer mixer,
int portIndex) {
super(info, mixer, null);
if (Printer.trace) Printer.trace("PortMixerPort CONSTRUCTOR: info: " + info);
this.portIndex = portIndex;
}
// ABSTRACT METHOD IMPLEMENTATIONS
// ABSTRACT LINE
void implOpen() throws LineUnavailableException {
if (Printer.trace) Printer.trace(">> PortMixerPort: implOpen().");
long newID = ((PortMixer) mixer).getID();
if ((id == 0) || (newID != id) || (controls.length == 0)) {
id = newID;
Vector vector = new Vector();
synchronized (vector) {
nGetControls(id, portIndex, vector);
controls = new Control[vector.size()];
for (int i = 0; i < controls.length; i++) {
controls[i] = (Control) vector.elementAt(i);
}
}
} else {
enableControls(controls, true);
}
if (Printer.trace) Printer.trace("<< PortMixerPort: implOpen() succeeded");
}
private void enableControls(Control[] controls, boolean enable) {
for (int i = 0; i < controls.length; i++) {
if (controls[i] instanceof BoolCtrl) {
((BoolCtrl) controls[i]).closed = !enable;
}
else if (controls[i] instanceof FloatCtrl) {
((FloatCtrl) controls[i]).closed = !enable;
}
else if (controls[i] instanceof CompoundControl) {
enableControls(((CompoundControl) controls[i]).getMemberControls(), enable);
}
}
}
private void disposeControls() {
enableControls(controls, false);
controls = new Control[0];
}
void implClose() {
if (Printer.trace) Printer.trace(">> PortMixerPort: implClose()");
// get rid of controls
enableControls(controls, false);
if (Printer.trace) Printer.trace("<< PortMixerPort: implClose() succeeded");
}
// METHOD OVERRIDES
// this is very similar to open(AudioFormat, int) in AbstractDataLine...
public void open() throws LineUnavailableException {
synchronized (mixer) {
// if the line is not currently open, try to open it with this format and buffer size
if (!isOpen()) {
if (Printer.trace) Printer.trace("> PortMixerPort: open");
// reserve mixer resources for this line
mixer.open(this);
try {
// open the line. may throw LineUnavailableException.
implOpen();
// if we succeeded, set the open state to true and send events
setOpen(true);
} catch (LineUnavailableException e) {
// release mixer resources for this line and then throw the exception
mixer.close(this);
throw e;
}
if (Printer.trace) Printer.trace("< PortMixerPort: open succeeded");
}
}
}
// this is very similar to close() in AbstractDataLine...
public void close() {
synchronized (mixer) {
if (isOpen()) {
if (Printer.trace) Printer.trace("> PortMixerPort.close()");
// set the open state to false and send events
setOpen(false);
// close resources for this line
implClose();
// release mixer resources for this line
mixer.close(this);
if (Printer.trace) Printer.trace("< PortMixerPort.close() succeeded");
}
}
}
} // class PortMixerPort
/**
* Private inner class representing a BooleanControl for PortMixerPort
*/
private static final class BoolCtrl extends BooleanControl {
// the handle to the native control function
private final long controlID;
private boolean closed = false;
private static BooleanControl.Type createType(String name) {
if (name.equals("Mute")) {
return BooleanControl.Type.MUTE;
}
else if (name.equals("Select")) {
// $$fb add as new static type?
//return BooleanControl.Type.SELECT;
}
return new BCT(name);
}
private BoolCtrl(long controlID, String name) {
this(controlID, createType(name));
}
private BoolCtrl(long controlID, BooleanControl.Type typ) {
super(typ, false);
this.controlID = controlID;
}
public void setValue(boolean value) {
if (!closed) {
nControlSetIntValue(controlID, value?1:0);
}
}
public boolean getValue() {
if (!closed) {
// never use any cached values
return (nControlGetIntValue(controlID)!=0)?true:false;
}
// ??
return false;
}
/**
* inner class for custom types
*/
private static final class BCT extends BooleanControl.Type {
private BCT(String name) {
super(name);
}
}
}
/**
* Private inner class representing a CompoundControl for PortMixerPort
*/
private static final class CompCtrl extends CompoundControl {
private CompCtrl(String name, Control[] controls) {
super(new CCT(name), controls);
}
/**
* inner class for custom compound control types
*/
private static final class CCT extends CompoundControl.Type {
private CCT(String name) {
super(name);
}
}
}
/**
* Private inner class representing a BooleanControl for PortMixerPort
*/
private static final class FloatCtrl extends FloatControl {
// the handle to the native control function
private final long controlID;
private boolean closed = false;
// predefined float control types. See also Ports.h
private final static FloatControl.Type[] FLOAT_CONTROL_TYPES = {
null,
FloatControl.Type.BALANCE,
FloatControl.Type.MASTER_GAIN,
FloatControl.Type.PAN,
FloatControl.Type.VOLUME
};
private FloatCtrl(long controlID, String name,
float min, float max, float precision, String units) {
this(controlID, new FCT(name), min, max, precision, units);
}
private FloatCtrl(long controlID, int type,
float min, float max, float precision, String units) {
this(controlID, FLOAT_CONTROL_TYPES[type], min, max, precision, units);
}
private FloatCtrl(long controlID, FloatControl.Type typ,
float min, float max, float precision, String units) {
super(typ, min, max, precision, 1000, min, units);
this.controlID = controlID;
}
public void setValue(float value) {
if (!closed) {
nControlSetFloatValue(controlID, value);
}
}
public float getValue() {
if (!closed) {
// never use any cached values
return nControlGetFloatValue(controlID);
}
// ??
return getMinimum();
}
/**
* inner class for custom types
*/
private static final class FCT extends FloatControl.Type {
private FCT(String name) {
super(name);
}
}
}
/**
* Private inner class representing a port info
*/
private static final class PortInfo extends Port.Info {
private PortInfo(String name, boolean isSource) {
super(Port.class, name, isSource);
}
}
// open the mixer with the given index. Returns a handle ID
private static native long nOpen(int mixerIndex) throws LineUnavailableException;
private static native void nClose(long id);
// gets the number of ports for this mixer
private static native int nGetPortCount(long id);
// gets the type of the port with this index
private static native int nGetPortType(long id, int portIndex);
// gets the name of the port with this index
private static native String nGetPortName(long id, int portIndex);
// fills the vector with the controls for this port
private static native void nGetControls(long id, int portIndex, Vector vector);
// getters/setters for controls
private static native void nControlSetIntValue(long controlID, int value);
private static native int nControlGetIntValue(long controlID);
private static native void nControlSetFloatValue(long controlID, float value);
private static native float nControlGetFloatValue(long controlID);
}