/* * AlsaDataLineMixer.java * * This file is part of Tritonus: http://www.tritonus.org/ */ /* * Copyright (c) 1999 - 2004 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 java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Clip; import javax.sound.sampled.DataLine; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.TargetDataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Line; import javax.sound.sampled.Mixer; import org.tritonus.share.TDebug; import org.tritonus.share.TSettings; import org.tritonus.share.sampled.mixer.TMixer; import org.tritonus.share.sampled.mixer.TMixerInfo; import org.tritonus.share.sampled.mixer.TSoftClip; import org.tritonus.share.GlobalInfo; import org.tritonus.lowlevel.alsa.Alsa; import org.tritonus.lowlevel.alsa.AlsaPcm; import org.tritonus.lowlevel.alsa.AlsaPcmHWParams; import org.tritonus.lowlevel.alsa.AlsaPcmHWParamsFormatMask; public class AlsaDataLineMixer extends TMixer { private static final AudioFormat[] EMPTY_AUDIOFORMAT_ARRAY = new AudioFormat[0]; private static final int CHANNELS_LIMIT = 32; // default buffer size in bytes. private static final int DEFAULT_BUFFER_SIZE = 32768; /* The name of the sound card this mixer is representing. */ private String m_strPcmName; public static String getDeviceNamePrefix() { if (TSettings.AlsaUsePlughw) { return "plughw"; } else { return "hw"; } } public static String getPcmName(int nCard) { String strPcmName = getDeviceNamePrefix() + ":" + nCard; if (TSettings.AlsaUsePlughw) { // strPcmName += ",0"; } return strPcmName; } public AlsaDataLineMixer() { this(0); } public AlsaDataLineMixer(int nCard) { this(getPcmName(nCard)); } public AlsaDataLineMixer(String strPcmName) { super(new TMixerInfo( "Alsa DataLine Mixer (" + strPcmName + ")", GlobalInfo.getVendor(), "Mixer for the Advanced Linux Sound Architecture (card " + strPcmName + ")", GlobalInfo.getVersion()), new Line.Info(Mixer.class)); if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.<init>(String): begin."); } m_strPcmName = strPcmName; List<AudioFormat> sourceFormats = getSupportedFormats(AlsaPcm.SND_PCM_STREAM_PLAYBACK); List<AudioFormat> targetFormats = getSupportedFormats(AlsaPcm.SND_PCM_STREAM_CAPTURE); List<Line.Info> sourceLineInfos = new ArrayList<Line.Info>(); Line.Info sourceLineInfo = new DataLine.Info( SourceDataLine.class, sourceFormats.toArray(EMPTY_AUDIOFORMAT_ARRAY), AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED); sourceLineInfos.add(sourceLineInfo); List<Line.Info> targetLineInfos = new ArrayList<Line.Info>(); Line.Info targetLineInfo = new DataLine.Info( TargetDataLine.class, targetFormats.toArray(EMPTY_AUDIOFORMAT_ARRAY), AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED); targetLineInfos.add(targetLineInfo); setSupportInformation(sourceFormats, targetFormats, sourceLineInfos, targetLineInfos); if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.<init>(String): end."); } } public String getPcmName() { return m_strPcmName; } //////////////// Line ////////////////////////////////////// // TODO: allow real close and reopen of mixer public void open() { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.open(): begin"); } // currently does nothing if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.open(): end"); } } public void close() { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.close(): begin"); } // currently does nothing if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.close(): end"); } } //////////////// Mixer ////////////////////////////////////// public int getMaxLines(Line.Info info) { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getMaxLines(): begin"); } // TODO: if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getMaxLines(): end"); } return 0; } //////////////// private ////////////////////////////////////// // nBufferSize is in bytes! protected SourceDataLine getSourceDataLine(AudioFormat format, int nBufferSize) throws LineUnavailableException { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSourceDataLine(): begin"); } if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSourceDataLine(): format: " + format); TDebug.out("AlsaDataLineMixer.getSourceDataLine(): buffer size: " + nBufferSize); } if (nBufferSize < 1) { nBufferSize = DEFAULT_BUFFER_SIZE; } AlsaSourceDataLine sourceDataLine = new AlsaSourceDataLine(this, format, nBufferSize); // sourceDataLine.start(); if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSourceDataLine(): returning: " + sourceDataLine); } if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSourceDataLine(): end"); } return sourceDataLine; } // nBufferSize is in bytes! protected TargetDataLine getTargetDataLine(AudioFormat format, int nBufferSize) throws LineUnavailableException { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getTargetDataLine(): begin"); } int nBufferSizeInBytes = nBufferSize * format.getFrameSize(); AlsaTargetDataLine targetDataLine = new AlsaTargetDataLine(this, format, nBufferSizeInBytes); // targetDataLine.start(); if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getTargetDataLine(): returning: " + targetDataLine); } if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getTargetDataLine(): end"); } return targetDataLine; } protected Clip getClip(AudioFormat format) throws LineUnavailableException { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getClip(): begin"); } Clip clip = new TSoftClip(this, format); if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getClip(): end"); } return clip; } /* nDirection: should be AlsaPcm.SND_PCM_STREAM_PLAYBACK or AlsaPcm.SND_PCM_STREAM_CAPTURE. */ private List<AudioFormat> getSupportedFormats(int nDirection) { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): begin"); } if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): direction: " + nDirection); } List<AudioFormat> supportedFormats = new ArrayList<AudioFormat>(); AlsaPcm alsaPcm = null; try { alsaPcm = new AlsaPcm( getPcmName(), nDirection, 0); // no special mode } catch (Exception e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } throw new RuntimeException("cannot open pcm"); } int nReturn; AlsaPcmHWParams hwParams = new AlsaPcmHWParams(); nReturn = alsaPcm.getAnyHWParams(hwParams); if (nReturn != 0) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): getAnyHWParams(): " + Alsa.getStringError(nReturn)); throw new RuntimeException(Alsa.getStringError(nReturn)); } AlsaPcmHWParamsFormatMask formatMask = new AlsaPcmHWParamsFormatMask(); int nMinChannels = hwParams.getChannelsMin(); if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): min channels: " + nMinChannels); } int nMaxChannels = hwParams.getChannelsMax(); nMaxChannels = Math.min(nMaxChannels, CHANNELS_LIMIT); if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): max channels: " + nMaxChannels); } hwParams.getFormatMask(formatMask); for (int i = 0; i < 32; i++) { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): checking ALSA format index: " + i); } if (formatMask.test(i)) { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): ...supported"); } AudioFormat audioFormat = AlsaUtils.getAlsaFormat(i); if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): adding AudioFormat: " + audioFormat); } addChanneledAudioFormats(supportedFormats, audioFormat, nMinChannels, nMaxChannels); // supportedFormats.add(audioFormat); } else { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): ...not supported"); } } } // TODO: close/free mask & hwParams? alsaPcm.close(); if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): end"); } return supportedFormats; } private static void addChanneledAudioFormats( Collection<AudioFormat> collection, AudioFormat protoAudioFormat, int nMinChannels, int nMaxChannels) { if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.addChanneledAudioFormats(): begin"); } for (int nChannels = nMinChannels; nChannels <= nMaxChannels; nChannels++) { AudioFormat channeledAudioFormat = getChanneledAudioFormat(protoAudioFormat, nChannels); if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.addChanneledAudioFormats(): adding AudioFormat: " + channeledAudioFormat); } collection.add(channeledAudioFormat); } if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.addChanneledAudioFormats(): end"); } } // TODO: better name // TODO: calculation of frame size is not perfect private static AudioFormat getChanneledAudioFormat(AudioFormat audioFormat, int nChannels) { AudioFormat channeledAudioFormat = new AudioFormat( audioFormat.getEncoding(), audioFormat.getSampleRate(), audioFormat.getSampleSizeInBits(), nChannels, (audioFormat.getSampleSizeInBits() / 8) * nChannels, audioFormat.getFrameRate(), audioFormat.isBigEndian()); return channeledAudioFormat; } } /*** AlsaDataLineMixer.java ***/