/* * AlsaSourceDataLine.java * * This file is part of Tritonus: http://www.tritonus.org/ */ /* * Copyright (c) 1999 - 2001 by Matthias Pfisterer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* |<--- this code is formatted to fit into 80 columns --->| */ package org.tritonus.sampled.mixer.alsa; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.DataLine; import javax.sound.sampled.FloatControl; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; import org.tritonus.share.TDebug; import org.tritonus.lowlevel.alsa.Alsa; import org.tritonus.lowlevel.alsa.AlsaPcm; import org.tritonus.share.sampled.TConversionTool; public class AlsaSourceDataLine extends AlsaBaseDataLine implements SourceDataLine { // private static final Class[] CONTROL_CLASSES = {GainControl.class}; private byte[] m_abSwapBuffer; // TODO: has info object to change if format or buffer size are changed later? // no, but it has to represent the mixer's capabilities. So a fixed info per mixer. public AlsaSourceDataLine(AlsaDataLineMixer mixer, AudioFormat format, int nBufferSize) throws LineUnavailableException { super(mixer, new DataLine.Info(SourceDataLine.class, format, nBufferSize)); if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.<init>(): begin"); } if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.<init>(): buffer size: " + nBufferSize); } if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.<init>(): end"); } } protected int getAlsaStreamType() { return AlsaPcm.SND_PCM_STREAM_PLAYBACK; } /* public void start() { setStarted(true); setActive(true); if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.start(): channel started."); } } */ protected void stopImpl() { if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.stopImpl(): called"); } int nReturn = 0; // int nReturn = getAlsaPcm().flushChannel(AlsaPcm.SND_PCM_CHANNEL_PLAYBACK); if (nReturn != 0) { TDebug.out("flushChannel: " + Alsa.getStringError(nReturn)); } // setStarted(false); } public int available() { // TODO: throwNYIException(); return -1; } // TODO: check if should block public int write(byte[] abData, int nOffset, int nLength) { if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.write(): begin"); } if (getSwapBytes()) { if (m_abSwapBuffer == null || m_abSwapBuffer.length < nOffset + nLength) { m_abSwapBuffer = new byte[nOffset + nLength]; } TConversionTool.changeOrderOrSign( abData, nOffset, m_abSwapBuffer, nOffset, nLength, getBytesPerSample()); abData = m_abSwapBuffer; } int nReturn = writeImpl(abData, nOffset, nLength); if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.write(): end"); } return nReturn; } /** Write data to the line. @param abData The buffer to use. @param nOffset @param nLength The length of the data that should be written, in bytes. Can be less that the length of abData. @return The number of bytes written. May be less than nLength. */ // TODO: check if should block private int writeImpl(byte[] abData, int nOffset, int nLength) { if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.writeImpl(): begin"); } if (nLength > 0 && !isActive()) { start(); } int nFrameSize = getFormat().getFrameSize(); int nRemaining = nLength; while (nRemaining > 0 && isOpen()) { synchronized (this) { if (!isOpen()) { return nLength - nRemaining; } if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.writeImpl(): trying to write (bytes): " + nRemaining); } int nRemainingFrames = nRemaining / nFrameSize; if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.writeImpl(): trying to write (frames): " + nRemainingFrames); } int nWrittenFrames = (int) getAlsaPcm().writei(abData, nOffset, nRemainingFrames); if (nWrittenFrames < 0) { TDebug.out("AlsaSourceDataLine.writeImpl(): " + Alsa.getStringError(nWrittenFrames)); return nLength - nRemaining; } if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.writeImpl(): written (frames): " + nWrittenFrames); } int nWrittenBytes = nWrittenFrames * nFrameSize; if (TDebug.TraceSourceDataLine) { TDebug.out("AlsaSourceDataLine.writeImpl(): written (bytes): " + nWrittenBytes); } nOffset += nWrittenBytes; nRemaining -= nWrittenBytes; } } return nLength; } public void drain() { // TODO: } public void flush() { // TODO: } /** * dGain is logarithmic!! */ protected void setGain(float dGain) { } /** Throw a RuntimeException saying "not yet implemented". */ private void throwNYIException() { throw new RuntimeException("sorry, this feature is not yet implemented"); } // IDEA: move inner classes to TBaseDataLine public class AlsaSourceDataLineGainControl extends FloatControl { /* * These variables should be static. However, Java 1.1 * doesn't allow this. So they aren't. */ private /*static*/ final float MAX_GAIN = 90.0F; private /*static*/ final float MIN_GAIN = -96.0F; // TODO: recheck this value private /*static*/ final int GAIN_INCREMENTS = 1000; // private float m_fGain; // private boolean m_bMuted; /*package*/ AlsaSourceDataLineGainControl() { super(FloatControl.Type.VOLUME, // or MASTER_GAIN ? -96.0F, // MIN_GAIN, 24.0F, // MAX_GAIN, 0.01F, // precision 0, // update period? 0.0F, // initial value "dB", "-96.0", "", "+24.0"); // m_bMuted = false; // should be included in a compund control? } public void setValue(float fGain) { fGain = Math.max(Math.min(fGain, getMaximum()), getMinimum()); if (Math.abs(fGain - getValue()) > 1.0E9) { super.setValue(fGain); // if (!getMute()) // { AlsaSourceDataLine.this.setGain(getValue()); // } } } /* public float getMaximum() { return MAX_GAIN; } public float getMinimum() { return MIN_GAIN; } public int getIncrements() { // TODO: check this value return GAIN_INCREMENTS; } public void fade(float fInitialGain, float fFinalGain, int nFrames) { // TODO: } public int getFadePrecision() { //TODO: return -1; } public boolean getMute() { return m_bMuted; } public void setMute(boolean bMuted) { if (bMuted != getMute()) { m_bMuted = bMuted; if (getMute()) { AlsaSourceDataLine.this.setGain(getMinimum()); } else { AlsaSourceDataLine.this.setGain(getGain()); } } } */ } } /*** AlsaSourceDataLine.java ***/