All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tritonus.share.sampled.file.TAudioOutputStream Maven / Gradle / Ivy

The newest version!
/*
 *	TAudioOutputStream.java
 *
 *	This file is part of Tritonus: http://www.tritonus.org/
 */

/*
 *  Copyright (c) 2000 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.share.sampled.file;

import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;

import static org.tritonus.share.sampled.AudioUtils.isPCM;
import static org.tritonus.share.sampled.TConversionTool.convertSign8;
import static org.tritonus.share.sampled.TConversionTool.swapOrder16;
import static org.tritonus.share.sampled.TConversionTool.swapOrder24;
import static org.tritonus.share.sampled.TConversionTool.swapOrder32;
import org.tritonus.share.TDebug;


/**	
 * Base class for classes implementing AudioOutputStream.
 *
 * @author Matthias Pfisterer
 */
 
public abstract class TAudioOutputStream
implements AudioOutputStream
{
	private AudioFormat		m_audioFormat;
	private long			m_lLength; // in bytes
	private long			m_lCalculatedLength;
	private TDataOutputStream	m_dataOutputStream;
	private boolean			m_bDoBackPatching;
	private boolean			m_bHeaderWritten;
	/** if this flag is set, do sign conversion for 8-bit PCM data */
	private boolean			m_doSignConversion;

	/** if this flag is set, do endian conversion for 16-bit PCM data */
	private boolean			m_doEndianConversion;

	protected TAudioOutputStream(AudioFormat audioFormat,
				     long lLength,
				     TDataOutputStream dataOutputStream,
				     boolean bDoBackPatching)
	{
		m_audioFormat = audioFormat;
		m_lLength = lLength;
		m_lCalculatedLength = 0;
		m_dataOutputStream = dataOutputStream;
		m_bDoBackPatching = bDoBackPatching;
		m_bHeaderWritten = false;
	}

	/**
	 * descendants should call this method if implicit sign conversion for 8-bit
	 * data should be done
	 */
	protected void requireSign8bit(boolean signed) {
		if (m_audioFormat.getSampleSizeInBits() == 8 && isPCM(m_audioFormat)) {
			boolean si = m_audioFormat.getEncoding().equals(
					AudioFormat.Encoding.PCM_SIGNED);
			m_doSignConversion = signed != si;
		}
	}

	/**
	 * descendants should call this method if implicit endian conversion should
	 * be done. Currently supported for 16, 24, and 32 bits per sample.
	 */
	protected void requireEndianness(boolean bigEndian) {
		int ssib = m_audioFormat.getSampleSizeInBits();
		if ((ssib == 16 || ssib == 24 || ssib == 32) && isPCM(m_audioFormat)) {
			m_doEndianConversion = bigEndian != m_audioFormat.isBigEndian();
		}
	}

	public AudioFormat getFormat()
	{
		return m_audioFormat;
	}



	/**	Gives length of the stream.
	 *	This value is in bytes. It may be AudioSystem.NOT_SPECIFIED
	 *	to express that the length is unknown.
	 */
	public long getLength()
	{
		return m_lLength;
	}



	/**	Gives number of bytes already written.
	 */
	// IDEA: rename this to BytesWritten or something like that ?
	public long getCalculatedLength()
	{
		return m_lCalculatedLength;
	}

	protected TDataOutputStream getDataOutputStream()
	{
		return m_dataOutputStream;
	}
	
	/** do sign or endianness conversion */
	private void handleImplicitConversions(byte[] abData, int nOffset, int nLength) {
		if (m_doSignConversion) {
			convertSign8(abData, nOffset, nLength);
		}
		if (m_doEndianConversion) {
			switch (m_audioFormat.getSampleSizeInBits()) {
			case 16: swapOrder16(abData, nOffset, nLength / 2);
				break;
			case 24: swapOrder24(abData, nOffset, nLength / 3);
				break;
			case 32:
				swapOrder32(abData, nOffset, nLength / 4);
				break;
			}
		}
	}


	/**	Writes audio data to the destination (file or output stream).
	 */
	// IDEA: use long?
	public int write(byte[] abData, int nOffset, int nLength)
		throws IOException
	{
		if (TDebug.TraceAudioOutputStream)
		{
			TDebug.out("TAudioOutputStream.write(): wanted length: " + nLength);
		}
		if (! m_bHeaderWritten)
		{
			writeHeader();
			m_bHeaderWritten = true;
		}
		// $$fb added
		// check that total writes do not exceed specified length
		long lTotalLength=getLength();
		if (lTotalLength!=AudioSystem.NOT_SPECIFIED && (m_lCalculatedLength+nLength)>lTotalLength) {
			if (TDebug.TraceAudioOutputStream) {
				TDebug.out("TAudioOutputStream.write(): requested more bytes to write than possible.");
			}
			nLength=(int) (lTotalLength-m_lCalculatedLength);
			// sanity
			if (nLength<0) {
				nLength=0;
			}
		}
		// TODO: throw an exception if nLength==0 ? (to indicate end of file ?)
		if (nLength>0) {
			handleImplicitConversions(abData, nOffset, nLength);
			m_dataOutputStream.write(abData, nOffset, nLength);
			m_lCalculatedLength += nLength;
			// if we converted something, need to undo the conversion to
			// guarantee integrity of data
			handleImplicitConversions(abData, nOffset, nLength);
		}
		if (TDebug.TraceAudioOutputStream)
		{
			TDebug.out("TAudioOutputStream.write(): calculated (total) length: " + m_lCalculatedLength+" bytes = "+(m_lCalculatedLength/getFormat().getFrameSize())+" frames");
		}
		return nLength;
	}



	/**	Writes the header of the audio file.
	 */
	protected abstract void writeHeader()
		throws IOException;



	/**	Closes the stream.
	 *	This does write remaining buffered data to the destination,
	 *	backpatch the header, if necessary, and closes the destination.
	 */
	public void close()
		throws IOException
	{
		if (TDebug.TraceAudioOutputStream)
		{
			TDebug.out("TAudioOutputStream.close(): called");
		}
		// flush?
		if (m_bDoBackPatching)
		{
			if (TDebug.TraceAudioOutputStream)
			{
				TDebug.out("TAudioOutputStream.close(): patching header");
			}
			patchHeader();
		}
		m_dataOutputStream.close();
	}



	protected void patchHeader()
		throws IOException
	{
		TDebug.out("TAudioOutputStream.patchHeader(): called");
		// DO NOTHING
	}



	protected void setLengthFromCalculatedLength()
	{
		m_lLength = m_lCalculatedLength;
	}
}



/*** TAudioOutputStream.java ***/




© 2015 - 2024 Weber Informatics LLC | Privacy Policy