530 lines
19 KiB
Java
530 lines
19 KiB
Java
/*
|
|
* Copyright (c) 2008, 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.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import javax.sound.sampled.AudioFormat;
|
|
import javax.sound.sampled.AudioInputStream;
|
|
import javax.sound.sampled.AudioSystem;
|
|
import javax.sound.sampled.Clip;
|
|
import javax.sound.sampled.Control;
|
|
import javax.sound.sampled.DataLine;
|
|
import javax.sound.sampled.Line;
|
|
import javax.sound.sampled.LineEvent;
|
|
import javax.sound.sampled.LineListener;
|
|
import javax.sound.sampled.LineUnavailableException;
|
|
import javax.sound.sampled.Mixer;
|
|
import javax.sound.sampled.SourceDataLine;
|
|
import javax.sound.sampled.AudioFormat.Encoding;
|
|
import javax.sound.sampled.Control.Type;
|
|
|
|
/**
|
|
* Software audio mixer
|
|
*
|
|
* @author Karl Helgason
|
|
*/
|
|
public final class SoftMixingMixer implements Mixer {
|
|
|
|
private static class Info extends Mixer.Info {
|
|
Info() {
|
|
super(INFO_NAME, INFO_VENDOR, INFO_DESCRIPTION, INFO_VERSION);
|
|
}
|
|
}
|
|
|
|
static final String INFO_NAME = "Gervill Sound Mixer";
|
|
|
|
static final String INFO_VENDOR = "OpenJDK Proposal";
|
|
|
|
static final String INFO_DESCRIPTION = "Software Sound Mixer";
|
|
|
|
static final String INFO_VERSION = "1.0";
|
|
|
|
static final Mixer.Info info = new Info();
|
|
|
|
final Object control_mutex = this;
|
|
|
|
boolean implicitOpen = false;
|
|
|
|
private boolean open = false;
|
|
|
|
private SoftMixingMainMixer mainmixer = null;
|
|
|
|
private AudioFormat format = new AudioFormat(44100, 16, 2, true, false);
|
|
|
|
private SourceDataLine sourceDataLine = null;
|
|
|
|
private SoftAudioPusher pusher = null;
|
|
|
|
private AudioInputStream pusher_stream = null;
|
|
|
|
private final float controlrate = 147f;
|
|
|
|
private final long latency = 100000; // 100 msec
|
|
|
|
private final boolean jitter_correction = false;
|
|
|
|
private final List<LineListener> listeners = new ArrayList<LineListener>();
|
|
|
|
private final javax.sound.sampled.Line.Info[] sourceLineInfo;
|
|
|
|
public SoftMixingMixer() {
|
|
|
|
sourceLineInfo = new javax.sound.sampled.Line.Info[2];
|
|
|
|
ArrayList<AudioFormat> formats = new ArrayList<AudioFormat>();
|
|
for (int channels = 1; channels <= 2; channels++) {
|
|
formats.add(new AudioFormat(Encoding.PCM_SIGNED,
|
|
AudioSystem.NOT_SPECIFIED, 8, channels, channels,
|
|
AudioSystem.NOT_SPECIFIED, false));
|
|
formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
|
|
AudioSystem.NOT_SPECIFIED, 8, channels, channels,
|
|
AudioSystem.NOT_SPECIFIED, false));
|
|
for (int bits = 16; bits < 32; bits += 8) {
|
|
formats.add(new AudioFormat(Encoding.PCM_SIGNED,
|
|
AudioSystem.NOT_SPECIFIED, bits, channels, channels
|
|
* bits / 8, AudioSystem.NOT_SPECIFIED, false));
|
|
formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
|
|
AudioSystem.NOT_SPECIFIED, bits, channels, channels
|
|
* bits / 8, AudioSystem.NOT_SPECIFIED, false));
|
|
formats.add(new AudioFormat(Encoding.PCM_SIGNED,
|
|
AudioSystem.NOT_SPECIFIED, bits, channels, channels
|
|
* bits / 8, AudioSystem.NOT_SPECIFIED, true));
|
|
formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
|
|
AudioSystem.NOT_SPECIFIED, bits, channels, channels
|
|
* bits / 8, AudioSystem.NOT_SPECIFIED, true));
|
|
}
|
|
formats.add(new AudioFormat(Encoding.PCM_FLOAT,
|
|
AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4,
|
|
AudioSystem.NOT_SPECIFIED, false));
|
|
formats.add(new AudioFormat(Encoding.PCM_FLOAT,
|
|
AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4,
|
|
AudioSystem.NOT_SPECIFIED, true));
|
|
formats.add(new AudioFormat(Encoding.PCM_FLOAT,
|
|
AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8,
|
|
AudioSystem.NOT_SPECIFIED, false));
|
|
formats.add(new AudioFormat(Encoding.PCM_FLOAT,
|
|
AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8,
|
|
AudioSystem.NOT_SPECIFIED, true));
|
|
}
|
|
AudioFormat[] formats_array = formats.toArray(new AudioFormat[formats
|
|
.size()]);
|
|
sourceLineInfo[0] = new DataLine.Info(SourceDataLine.class,
|
|
formats_array, AudioSystem.NOT_SPECIFIED,
|
|
AudioSystem.NOT_SPECIFIED);
|
|
sourceLineInfo[1] = new DataLine.Info(Clip.class, formats_array,
|
|
AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED);
|
|
}
|
|
|
|
public Line getLine(Line.Info info) throws LineUnavailableException {
|
|
|
|
if (!isLineSupported(info))
|
|
throw new IllegalArgumentException("Line unsupported: " + info);
|
|
|
|
if ((info.getLineClass() == SourceDataLine.class)) {
|
|
return new SoftMixingSourceDataLine(this, (DataLine.Info) info);
|
|
}
|
|
if ((info.getLineClass() == Clip.class)) {
|
|
return new SoftMixingClip(this, (DataLine.Info) info);
|
|
}
|
|
|
|
throw new IllegalArgumentException("Line unsupported: " + info);
|
|
}
|
|
|
|
public int getMaxLines(Line.Info info) {
|
|
if (info.getLineClass() == SourceDataLine.class)
|
|
return AudioSystem.NOT_SPECIFIED;
|
|
if (info.getLineClass() == Clip.class)
|
|
return AudioSystem.NOT_SPECIFIED;
|
|
return 0;
|
|
}
|
|
|
|
public javax.sound.sampled.Mixer.Info getMixerInfo() {
|
|
return info;
|
|
}
|
|
|
|
public javax.sound.sampled.Line.Info[] getSourceLineInfo() {
|
|
Line.Info[] localArray = new Line.Info[sourceLineInfo.length];
|
|
System.arraycopy(sourceLineInfo, 0, localArray, 0,
|
|
sourceLineInfo.length);
|
|
return localArray;
|
|
}
|
|
|
|
public javax.sound.sampled.Line.Info[] getSourceLineInfo(
|
|
javax.sound.sampled.Line.Info info) {
|
|
int i;
|
|
ArrayList<javax.sound.sampled.Line.Info> infos = new ArrayList<javax.sound.sampled.Line.Info>();
|
|
|
|
for (i = 0; i < sourceLineInfo.length; i++) {
|
|
if (info.matches(sourceLineInfo[i])) {
|
|
infos.add(sourceLineInfo[i]);
|
|
}
|
|
}
|
|
return infos.toArray(new Line.Info[infos.size()]);
|
|
}
|
|
|
|
public Line[] getSourceLines() {
|
|
|
|
Line[] localLines;
|
|
|
|
synchronized (control_mutex) {
|
|
|
|
if (mainmixer == null)
|
|
return new Line[0];
|
|
SoftMixingDataLine[] sourceLines = mainmixer.getOpenLines();
|
|
|
|
localLines = new Line[sourceLines.length];
|
|
|
|
for (int i = 0; i < localLines.length; i++) {
|
|
localLines[i] = sourceLines[i];
|
|
}
|
|
}
|
|
|
|
return localLines;
|
|
}
|
|
|
|
public javax.sound.sampled.Line.Info[] getTargetLineInfo() {
|
|
return new javax.sound.sampled.Line.Info[0];
|
|
}
|
|
|
|
public javax.sound.sampled.Line.Info[] getTargetLineInfo(
|
|
javax.sound.sampled.Line.Info info) {
|
|
return new javax.sound.sampled.Line.Info[0];
|
|
}
|
|
|
|
public Line[] getTargetLines() {
|
|
return new Line[0];
|
|
}
|
|
|
|
public boolean isLineSupported(javax.sound.sampled.Line.Info info) {
|
|
if (info != null) {
|
|
for (int i = 0; i < sourceLineInfo.length; i++) {
|
|
if (info.matches(sourceLineInfo[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean isSynchronizationSupported(Line[] lines, boolean maintainSync) {
|
|
return false;
|
|
}
|
|
|
|
public void synchronize(Line[] lines, boolean maintainSync) {
|
|
throw new IllegalArgumentException(
|
|
"Synchronization not supported by this mixer.");
|
|
}
|
|
|
|
public void unsynchronize(Line[] lines) {
|
|
throw new IllegalArgumentException(
|
|
"Synchronization not supported by this mixer.");
|
|
}
|
|
|
|
public void addLineListener(LineListener listener) {
|
|
synchronized (control_mutex) {
|
|
listeners.add(listener);
|
|
}
|
|
}
|
|
|
|
private void sendEvent(LineEvent event) {
|
|
if (listeners.size() == 0)
|
|
return;
|
|
LineListener[] listener_array = listeners
|
|
.toArray(new LineListener[listeners.size()]);
|
|
for (LineListener listener : listener_array) {
|
|
listener.update(event);
|
|
}
|
|
}
|
|
|
|
public void close() {
|
|
if (!isOpen())
|
|
return;
|
|
|
|
sendEvent(new LineEvent(this, LineEvent.Type.CLOSE,
|
|
AudioSystem.NOT_SPECIFIED));
|
|
|
|
SoftAudioPusher pusher_to_be_closed = null;
|
|
AudioInputStream pusher_stream_to_be_closed = null;
|
|
synchronized (control_mutex) {
|
|
if (pusher != null) {
|
|
pusher_to_be_closed = pusher;
|
|
pusher_stream_to_be_closed = pusher_stream;
|
|
pusher = null;
|
|
pusher_stream = null;
|
|
}
|
|
}
|
|
|
|
if (pusher_to_be_closed != null) {
|
|
// Pusher must not be closed synchronized against control_mutex
|
|
// this may result in synchronized conflict between pusher and
|
|
// current thread.
|
|
pusher_to_be_closed.stop();
|
|
|
|
try {
|
|
pusher_stream_to_be_closed.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
synchronized (control_mutex) {
|
|
|
|
if (mainmixer != null)
|
|
mainmixer.close();
|
|
open = false;
|
|
|
|
if (sourceDataLine != null) {
|
|
sourceDataLine.drain();
|
|
sourceDataLine.close();
|
|
sourceDataLine = null;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public Control getControl(Type control) {
|
|
throw new IllegalArgumentException("Unsupported control type : "
|
|
+ control);
|
|
}
|
|
|
|
public Control[] getControls() {
|
|
return new Control[0];
|
|
}
|
|
|
|
public javax.sound.sampled.Line.Info getLineInfo() {
|
|
return new Line.Info(Mixer.class);
|
|
}
|
|
|
|
public boolean isControlSupported(Type control) {
|
|
return false;
|
|
}
|
|
|
|
public boolean isOpen() {
|
|
synchronized (control_mutex) {
|
|
return open;
|
|
}
|
|
}
|
|
|
|
public void open() throws LineUnavailableException {
|
|
if (isOpen()) {
|
|
implicitOpen = false;
|
|
return;
|
|
}
|
|
open(null);
|
|
}
|
|
|
|
public void open(SourceDataLine line) throws LineUnavailableException {
|
|
if (isOpen()) {
|
|
implicitOpen = false;
|
|
return;
|
|
}
|
|
synchronized (control_mutex) {
|
|
|
|
try {
|
|
|
|
if (line != null)
|
|
format = line.getFormat();
|
|
|
|
AudioInputStream ais = openStream(getFormat());
|
|
|
|
if (line == null) {
|
|
synchronized (SoftMixingMixerProvider.mutex) {
|
|
SoftMixingMixerProvider.lockthread = Thread
|
|
.currentThread();
|
|
}
|
|
|
|
try {
|
|
Mixer defaultmixer = AudioSystem.getMixer(null);
|
|
if (defaultmixer != null)
|
|
{
|
|
// Search for suitable line
|
|
|
|
DataLine.Info idealinfo = null;
|
|
AudioFormat idealformat = null;
|
|
|
|
Line.Info[] lineinfos = defaultmixer.getSourceLineInfo();
|
|
idealFound:
|
|
for (int i = 0; i < lineinfos.length; i++) {
|
|
if(lineinfos[i].getLineClass() == SourceDataLine.class)
|
|
{
|
|
DataLine.Info info = (DataLine.Info)lineinfos[i];
|
|
AudioFormat[] formats = info.getFormats();
|
|
for (int j = 0; j < formats.length; j++) {
|
|
AudioFormat format = formats[j];
|
|
if(format.getChannels() == 2 ||
|
|
format.getChannels() == AudioSystem.NOT_SPECIFIED)
|
|
if(format.getEncoding().equals(Encoding.PCM_SIGNED) ||
|
|
format.getEncoding().equals(Encoding.PCM_UNSIGNED))
|
|
if(format.getSampleRate() == AudioSystem.NOT_SPECIFIED ||
|
|
format.getSampleRate() == 48000.0)
|
|
if(format.getSampleSizeInBits() == AudioSystem.NOT_SPECIFIED ||
|
|
format.getSampleSizeInBits() == 16)
|
|
{
|
|
idealinfo = info;
|
|
int ideal_channels = format.getChannels();
|
|
boolean ideal_signed = format.getEncoding().equals(Encoding.PCM_SIGNED);
|
|
float ideal_rate = format.getSampleRate();
|
|
boolean ideal_endian = format.isBigEndian();
|
|
int ideal_bits = format.getSampleSizeInBits();
|
|
if(ideal_bits == AudioSystem.NOT_SPECIFIED) ideal_bits = 16;
|
|
if(ideal_channels == AudioSystem.NOT_SPECIFIED) ideal_channels = 2;
|
|
if(ideal_rate == AudioSystem.NOT_SPECIFIED) ideal_rate = 48000;
|
|
idealformat = new AudioFormat(ideal_rate, ideal_bits,
|
|
ideal_channels, ideal_signed, ideal_endian);
|
|
break idealFound;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(idealformat != null)
|
|
{
|
|
format = idealformat;
|
|
line = (SourceDataLine) defaultmixer.getLine(idealinfo);
|
|
}
|
|
}
|
|
|
|
if(line == null)
|
|
line = AudioSystem.getSourceDataLine(format);
|
|
} finally {
|
|
synchronized (SoftMixingMixerProvider.mutex) {
|
|
SoftMixingMixerProvider.lockthread = null;
|
|
}
|
|
}
|
|
|
|
if (line == null)
|
|
throw new IllegalArgumentException("No line matching "
|
|
+ info.toString() + " is supported.");
|
|
}
|
|
|
|
double latency = this.latency;
|
|
|
|
if (!line.isOpen()) {
|
|
int bufferSize = getFormat().getFrameSize()
|
|
* (int) (getFormat().getFrameRate() * (latency / 1000000f));
|
|
line.open(getFormat(), bufferSize);
|
|
|
|
// Remember that we opened that line
|
|
// so we can close again in SoftSynthesizer.close()
|
|
sourceDataLine = line;
|
|
}
|
|
if (!line.isActive())
|
|
line.start();
|
|
|
|
int controlbuffersize = 512;
|
|
try {
|
|
controlbuffersize = ais.available();
|
|
} catch (IOException e) {
|
|
}
|
|
|
|
// Tell mixer not fill read buffers fully.
|
|
// This lowers latency, and tells DataPusher
|
|
// to read in smaller amounts.
|
|
// mainmixer.readfully = false;
|
|
// pusher = new DataPusher(line, ais);
|
|
|
|
int buffersize = line.getBufferSize();
|
|
buffersize -= buffersize % controlbuffersize;
|
|
|
|
if (buffersize < 3 * controlbuffersize)
|
|
buffersize = 3 * controlbuffersize;
|
|
|
|
if (jitter_correction) {
|
|
ais = new SoftJitterCorrector(ais, buffersize,
|
|
controlbuffersize);
|
|
}
|
|
pusher = new SoftAudioPusher(line, ais, controlbuffersize);
|
|
pusher_stream = ais;
|
|
pusher.start();
|
|
|
|
} catch (LineUnavailableException e) {
|
|
if (isOpen())
|
|
close();
|
|
throw new LineUnavailableException(e.toString());
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public AudioInputStream openStream(AudioFormat targetFormat)
|
|
throws LineUnavailableException {
|
|
|
|
if (isOpen())
|
|
throw new LineUnavailableException("Mixer is already open");
|
|
|
|
synchronized (control_mutex) {
|
|
|
|
open = true;
|
|
|
|
implicitOpen = false;
|
|
|
|
if (targetFormat != null)
|
|
format = targetFormat;
|
|
|
|
mainmixer = new SoftMixingMainMixer(this);
|
|
|
|
sendEvent(new LineEvent(this, LineEvent.Type.OPEN,
|
|
AudioSystem.NOT_SPECIFIED));
|
|
|
|
return mainmixer.getInputStream();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public void removeLineListener(LineListener listener) {
|
|
synchronized (control_mutex) {
|
|
listeners.remove(listener);
|
|
}
|
|
}
|
|
|
|
public long getLatency() {
|
|
synchronized (control_mutex) {
|
|
return latency;
|
|
}
|
|
}
|
|
|
|
public AudioFormat getFormat() {
|
|
synchronized (control_mutex) {
|
|
return format;
|
|
}
|
|
}
|
|
|
|
float getControlRate() {
|
|
return controlrate;
|
|
}
|
|
|
|
SoftMixingMainMixer getMainMixer() {
|
|
if (!isOpen())
|
|
return null;
|
|
return mainmixer;
|
|
}
|
|
|
|
}
|