/* * This file is modified by Ivan Maidanski <ivmai@ivmaisoft.com> * Project name: JCGO-SUNAWT (http://www.ivmaisoft.com/jcgo/) */ /* * @(#)SimpleOutputDevice.java 1.27 03/01/23 * * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package com.sun.media.sound; import java.util.Vector; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Control; import javax.sound.sampled.DataLine; import javax.sound.sampled.Line; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.Port; import javax.sound.sampled.SourceDataLine; /** * Simple output device. Contains mixer and data line functionality for * simple wave-out output devices. * * 1.27 03/01/23 * @author Kara Kytle */ class SimpleOutputDevice extends AbstractMixer { // INSTANCE PROPERTIES private AudioFormat format = null; private int bufferSize = AudioSystem.NOT_SPECIFIED; /** * Fixed set of ports available on the output device. */ private Port[] ports; // CONSTRUCTOR /** * Constructs a new SimpleOutputDevice. */ SimpleOutputDevice(SimpleOutputDeviceProvider.OutputDeviceInfo outputDeviceInfo) { super(outputDeviceInfo, // Mixer.Info null, // Control[] null, // Line.Info[] sourceLineInfo null); // Line.Info[] targetLineInfo if (Printer.trace) Printer.trace(">> SimpleOutputDevice: constructor"); // initialize platform-specific values, and load the native library Platform.initialize(); // query our source line format capabilities // $$kk: 06.05.99 this is bad; should query the full set of supported formats boolean supports8 = nSupportsSampleSizeInBits(8); boolean supports16 = nSupportsSampleSizeInBits(16); boolean supportsMono = nSupportsChannels(1); boolean supportsStereo = nSupportsChannels(2); boolean supports8k = nSupportsSampleRate(8000.0f); boolean supports11k = nSupportsSampleRate(11025.0f); //$$fb 2001-07-20: added "support" for 16KHz boolean supports16k = nSupportsSampleRate(16000.0f); boolean supports22k = nSupportsSampleRate(22050.0f); //$$fb 2001-07-20: added "support" for 32KHz boolean supports32k = nSupportsSampleRate(32000.0f); boolean supports44k = nSupportsSampleRate(44100.0f); // $$kk: 06.02.99: we should handle the full set of formats // exposed by the device!! we should also handle direct u-law // output where supported. we should not be doing this stupid // query!! this probably means we should return an array of // supported AudioFormat objects from a single native call. // populate a vector of supported formats Vector formats = new Vector(); // $$fb: 2001-07-05 fixed incorrect framesize values. Bug 4469409 // $$fb: 2001-07-20 added support for 16KHz and 32KHz. Bug 4479441 // 8000k if (supports8 && supportsMono && supports8k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000, 8, 1, 1, 8000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 8000, 8, 1, 1, 8000, false)); } if (supports8 && supportsStereo && supports8k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000, 8, 2, 2, 8000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 8000, 8, 2, 2, 8000, false)); } if (supports16 && supportsMono && supports8k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000, 16, 1, 2, 8000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000, 16, 1, 2, 8000, true)); } if (supports16 && supportsStereo && supports8k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000, 16, 2, 4, 8000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000, 16, 2, 4, 8000, true)); } // 11025 if (supports8 && supportsMono && supports11k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 11025, 8, 1, 1, 11025, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 11025, 8, 1, 1, 11025, false)); } if (supports8 && supportsStereo && supports11k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 11025, 8, 2, 2, 11025, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 11025, 8, 2, 2, 11025, false)); } //$$fb 2001-07-20: added "support" for 16KHz // 16000 if (supports16 && supportsMono && supports11k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 11025, 16, 1, 2, 11025, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 11025, 16, 1, 2, 11025, true)); } if (supports16 && supportsStereo && supports11k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 11025, 16, 2, 4, 11025, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 11025, 16, 2, 4, 11025, true)); } if (supports8 && supportsMono && supports16k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 16000, 8, 1, 1, 16000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 16000, 8, 1, 1, 16000, false)); } if (supports8 && supportsStereo && supports16k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 16000, 8, 2, 2, 16000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 16000, 8, 2, 2, 16000, false)); } if (supports16 && supportsMono && supports16k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 16000, 16, 1, 2, 16000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 16000, 16, 1, 2, 16000, true)); } if (supports16 && supportsStereo && supports16k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 16000, 16, 2, 4, 16000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 16000, 16, 2, 4, 16000, true)); } // 22050 if (supports8 && supportsMono && supports22k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 22050, 8, 1, 1, 22050, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 22050, 8, 1, 1, 22050, false)); } if (supports8 && supportsStereo && supports22k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 22050, 8, 2, 2, 22050, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 22050, 8, 2, 2, 22050, false)); } if (supports16 && supportsMono && supports22k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 22050, 16, 1, 2, 22050, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 22050, 16, 1, 2, 22050, true)); } if (supports16 && supportsStereo && supports22k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 22050, 16, 2, 4, 22050, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 22050, 16, 2, 4, 22050, true)); } //$$fb 2001-07-20: added "support" for 32KHz // 32000 if (supports8 && supportsMono && supports32k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 32000, 8, 1, 1, 32000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 32000, 8, 1, 1, 32000, false)); } if (supports8 && supportsStereo && supports32k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 32000, 8, 2, 2, 32000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 32000, 8, 2, 2, 32000, false)); } if (supports16 && supportsMono && supports32k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 32000, 16, 1, 2, 32000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 32000, 16, 1, 2, 32000, true)); } if (supports16 && supportsStereo && supports32k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 32000, 16, 2, 4, 32000, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 32000, 16, 2, 4, 32000, true)); } // 44100 if (supports8 && supportsMono && supports44k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 8, 1, 1, 44100, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 44100, 8, 1, 1, 44100, false)); } if (supports8 && supportsStereo && supports44k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 8, 2, 2, 44100, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 44100, 8, 2, 2, 44100, false)); } if (supports16 && supportsMono && supports44k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 1, 2, 44100, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 1, 2, 44100, true)); } if (supports16 && supportsStereo && supports44k) { formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false)); formats.addElement(new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, true)); } // $$kk: 06.03.99: this is stupid; should store these as an array!! AudioFormat[] formatArray; synchronized(formats) { formatArray = new AudioFormat[formats.size()]; for (int i = 0; i < formatArray.length; i++) { formatArray[i] = (AudioFormat)formats.elementAt(i); } } // $$kk: 05.31.99: need to figure this out! // set up the port info objects; these are the targets for the output device int numPorts = nGetNumPorts(); targetLineInfo = new Port.Info[numPorts]; ports = new Port[numPorts]; String name; for (int i = 0; i < numPorts; i++) { name = nGetPortName(i); targetLineInfo[i] = getPortInfo(name); ports[i] = new OutputDevicePort((Port.Info)targetLineInfo[i], this, new Control[0]); } // set up the source line objects sourceLineInfo = new DataLine.Info[1]; sourceLineInfo[0] = new DataLine.Info(SourceDataLine.class, formatArray, 0, AudioSystem.NOT_SPECIFIED); // $$jb: 12.07.99: Fix for 4297115: NoSuchElementException thrown on // machines without capture devices. We need to make sure that the // formats vector has elements before we set the initial format. // set the initial format as the best supported one if( formats.size() > 0 ) { format = (AudioFormat)formats.lastElement(); } else { format = null; } if (Printer.trace) Printer.trace("<< SimpleOutputDevice: constructor completed"); } // ABSTRACT MIXER: ABSTRACT METHOD IMPLEMENTATIONS // MIXER METHODS public Line getLine(Line.Info info) throws LineUnavailableException { Line.Info fullInfo = getLineInfo(info); if (fullInfo == null) { throw new IllegalArgumentException("Line unsupported: " + info); } if (fullInfo instanceof Port.Info) { for (int i = 0; i < ports.length; i++) { if (fullInfo.equals(ports[i].getLineInfo())) { return ports[i]; } } } if (fullInfo instanceof DataLine.Info) { DataLine.Info dataLineInfo = (DataLine.Info)info; if (dataLineInfo.getLineClass().isAssignableFrom(OutputDeviceDataLine.class)) { AudioFormat[] supportedFormats = dataLineInfo.getFormats(); AudioFormat defaultFormat = supportedFormats[supportedFormats.length-1]; return new OutputDeviceDataLine(dataLineInfo, this, defaultFormat, dataLineInfo.getMaxBufferSize()); } } 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; } // $$kk: 06.02.99: for ports, let's assume we can only have one output // port open at a time. the real situation may be more complicated. if (fullInfo instanceof Port.Info) { return ports.length; } // $$kk: 06.02.99: just one for now; we're really not going // to let anyone use it.,,, if (fullInfo instanceof DataLine.Info) { return 1; } return 0; } // ABSTRACT LINE: ABSTRACT METHOD IMPLEMENTATIONS protected void implOpen() throws LineUnavailableException { //$$fb 2001-08-01: better check for buffer size. Part of fix for bug #4326534 (flush bug) int bufferSizeInFrames=(bufferSize==AudioSystem.NOT_SPECIFIED)? AudioSystem.NOT_SPECIFIED:(bufferSize / format.getFrameSize()); if (bufferSizeInFrames <= 0) { bufferSizeInFrames = (int) (format.getFrameRate() / 2); } // open the output device // note that this method takes the buffer size in frames nOpen(((SimpleOutputDeviceProvider.OutputDeviceInfo)getMixerInfo()).getIndex(), (int)format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels(), bufferSizeInFrames ); //$$fb 2001-08-01: make sure that the bufferSize field has correct and aligned buffersize this.bufferSize = (bufferSizeInFrames==AudioSystem.NOT_SPECIFIED)? AudioSystem.NOT_SPECIFIED:(bufferSizeInFrames * format.getFrameSize()); } public void implClose() { // close the output device nClose(); } // ABSTRACT DATA LINE: ABSTRACT METHOD IMPLEMENTATIONS /** * Start the output device */ protected void implStart() { nStart(); } /** * Stop the output device */ protected void implStop() { nStart(); } // HELPER METHODS /** * Utility method for converting between String names and well-known * Port types. I'm only doing the ones that can be targets. We may * want to do something at least a little more general here.... */ private Port.Info getPortInfo(String name) { if (name.equals(Port.Info.SPEAKER.toString())) { return Port.Info.SPEAKER; } if (name.equals(Port.Info.HEADPHONE.toString())) { return Port.Info.HEADPHONE; } if (name.equals(Port.Info.LINE_OUT.toString())) { return Port.Info.LINE_OUT; } // return a new port info object. return (new OutputDevicePortInfo(name)); } // INNER CLASSES /** * Private inner class representing the source data line for the SimpleOutputDevice. */ private class OutputDeviceDataLine extends AbstractDataLine implements SourceDataLine { private CircularBuffer circularBuffer = null; // CONSTRUCTOR private OutputDeviceDataLine(DataLine.Info info, SimpleOutputDevice mixer, AudioFormat initialFormat, int initialBufferSize) { // $$kk: 06.02.99: need to deal with controls! super(info, mixer, null, initialFormat, initialBufferSize); } // SOURCE DATA LINE METHODS /** * This will generate a NullPointerException if b is null, and * an ArrayIndexOutOfBounds exception if len frames past off * overruns the end of the array. * @param off - in bytes * @param len - in sample frames */ public int write(byte[] b, int off, int len) { // fail; this doesn't really work throw new SecurityException("Permission to read data from the output device not granted."); } public int available() { // fail; this doesn't really work return 0; } // ABSTRACT METHOD IMPLEMENTATIONS // ABSTRACT LINE void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException { // fail; this doesn't really work throw new SecurityException("Permission to read data from the output device not granted."); } void implClose() { // fail; this doesn't really work throw new SecurityException("Permission to read data from the output device not granted."); } // ABSTRACT DATA LINE void implStart() { // fail; this doesn't really work throw new SecurityException("Permission to read data from the output device not granted."); } void implStop() { // fail; this doesn't really work throw new SecurityException("Permission to read data from the output device not granted."); } // METHODS FOR INTERNAL IMPLEMENTATION USE private CircularBuffer getCircularBuffer() { return circularBuffer; } } // class OutputDeviceDataLine /** * Private inner class representing a port on the output device. */ private class OutputDevicePort extends AbstractLine implements Port { private OutputDevicePort(Port.Info info, AbstractMixer mixer, Control[] controls) { super(info, mixer, controls); } public void open() throws LineUnavailableException { /* $$kk: 06.03.99: need to implement */ } public void close() { /* $$kk: 06.03.99: need to implement */ } } // class OutputDevicePort /** * Private inner class representing an output device port info object */ private static class OutputDevicePortInfo extends Port.Info { private OutputDevicePortInfo(String name) { super(Port.class, name, false); } } // NATIVE METHODS // this will open the output device and start the output thread // note that this takes the buffer size in frames. // GM_ResumeGeneralSound private void nOpen(int index, float sampleRate, int sampleSizeInBits, int channels, int bufferSize) throws LineUnavailableException { throw new LineUnavailableException(); } // this will close the output device and stop the output thread // GM_PauseGeneralSound private void nClose() {} // ? private void nStart() {} // ? private void nStop() {} // these should be replaced by a better mechanism for finding supported // formats private boolean nSupportsSampleRate(float sampleRate) { return (sampleRate == 8000) || (sampleRate == 11025) || (sampleRate == 16000) || (sampleRate == 22050) || (sampleRate == 32000) || (sampleRate == 44100) || (sampleRate == 48000); } private boolean nSupportsSampleSizeInBits(int sampleSizeInBits) { return sampleSizeInBits == 16 || sampleSizeInBits == 8; } private boolean nSupportsChannels(int channels) { return channels == 1 || channels == 2; } // gets the number of ports private int nGetNumPorts() { return 0; } // gets the name of the port with this index private String nGetPortName(int index) { return null; } //private native void nDrain(); //private native void nFlush(); //private native long nGetPosition(); //private native void nClose(); //private native void nPause(); //private native void nResume(); // $$kk: 03.25.99: need to implement! //private static /*native*/ int nGetNumDevices() { // return 1; //} }