OpenSolaris_b135/cmd/audio/utilities/Audio.cc

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1993-2001 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <Audio.h>
#include <AudioDebug.h>
#include <AudioBuffer.h>

// class Audio methods


// Initialize monotonically increasing id counter
int
Audio::idctr = 0;

// Constructor
Audio::
Audio(
	const char	*str):				// name
	id(++idctr), refcnt(0), readpos(0.), writepos(0.), errorfunc(0)
{
	char		*s;

	s = (char *)((str == NULL) ? "" : str);
	name = new char[strlen(s) + 1];
	(void) strcpy(name, s);

#ifndef DEBUG
	// errorfunc is always set if compiling DEBUG;
	// otherwise, only if requested
	if (GetDebug() > 0)
#endif
		errorfunc = AudioStderrMsg;
	PrintMsg(_MGET_("Audio object create"), InitMessage);
}

// Destructor
Audio::
~Audio()
{
	// If there are outstanding references, there is a programming error
	if (refcnt < 0) {
		PrintMsg(_MGET_("Audio object multiple destroy"), InitFatal);
	} else if (refcnt > 0) {
		PrintMsg(_MGET_("Referenced Audio object destroyed"),
		    InitFatal);
	} else {
		refcnt = -1;
		PrintMsg(_MGET_("Audio object destroy"), InitMessage);
	}
	delete name;
}

// Raise error code
AudioError Audio::
RaiseError(
	AudioError	code,			// error code
	AudioSeverity	sev,			// error severity
	char		*msg) const		// additional message
{
	if (code == AUDIO_SUCCESS)
		return (code);

	if (errorfunc != 0) {
		// XXX - Userfunc return value ignored for now
		(void) (*errorfunc)(this, code, sev, msg);
	}
	if ((sev == Fatal) || (sev == InitFatal))
		abort();
	return (code);
}

// Print out messages
void Audio::
PrintMsg(
	char		*msg,			// error message
	AudioSeverity	sev) const		// error severity
{
	if (errorfunc != 0) {
		// XXX - Userfunc return value ignored for now
		(void) (*errorfunc)(this, AUDIO_NOERROR, sev, msg);
	}

	if ((sev == Fatal) || (sev == InitFatal)) {
		fprintf(stderr, _MGET_("** Fatal Error: %s\n"), msg);
		abort();
	}
}

// Increment reference count
void Audio::
Reference()
{
	if (refcnt < 0) {
		PrintMsg(_MGET_("Reference to destroyed Audio object"), Fatal);
	} else {
		refcnt++;
	}
}

// Decrement reference count
void Audio::
Dereference()
{
	if (refcnt < 0) {
		PrintMsg(_MGET_("Dereference of destroyed Audio object"),
		    Fatal);
	} else if (refcnt == 0) {
		PrintMsg(_MGET_("Audio object dereference underflow"), Fatal);
	} else if (--refcnt == 0) {	// If this was the last reference,
		delete this;		//  blow the object away
	}
}

// Reset the stored name
void Audio::
SetName(
	const char	*str)		// new name string
{
	delete name;
	name = new char[strlen(str) + 1];
	(void) strcpy(name, str);
}


// Set the current read/write position pointer
Double Audio::
setpos(
	Double&	pos,			// field to update
	Double	newpos,			// new position
	Whence	w)			// Absolute || Relative || Relative_eof
{
	if (w == Relative)			// offset from current position
		newpos += pos;
	else if (w == Relative_eof) {		// offset from end-of-file
		if (!Undefined(GetLength()))
			newpos += GetLength();
		else
			return (AUDIO_UNKNOWN_TIME);
	}

	// If seek before start of file, set to start of file
	if (newpos < 0.)
		newpos = 0.;
	pos = newpos;
	return (pos);
}

// Set a new read position
Double Audio::
SetReadPosition(
	Double		pos,		// new position or offset
	Whence		w)		// Absolute | Relative
{
	return (setpos(readpos, pos, w));
}

// Set a new write position
Double Audio::
SetWritePosition(
	Double		pos,		// new position or offset
	Whence		w)		// Absolute | Relative
{
	return (setpos(writepos, pos, w));
}

// Default read routine reads from the current position
AudioError Audio::
Read(
	void*		buf,			// buffer address
	size_t&		len)			// buffer length (updated)
{
	// ReadData updates the position argument
	return (ReadData(buf, len, readpos));
}

// Default write routine writes to the current position
AudioError Audio::
Write(
	void*		buf,			// buffer address
	size_t&		len)			// buffer length (updated)
{
	// WriteData updates the position argument
	return (WriteData(buf, len, writepos));
}

// Default append routine should be specialized, if the object is fixed-length
AudioError Audio::
AppendData(
	void*		buf,			// buffer address
	size_t&		len,			// buffer length (updated)
	Double&		pos)			// write position (updated)
{
	// The default action is just to write the data.
	// Subclasses, like AudioBuffer, should specialize this method
	// to extend the object, if necessary.
	return (WriteData(buf, len, pos));
}

// Copy out to the specified audio object.
// Input and output positions default to the 'current' positions.
AudioError Audio::
Copy(
	Audio*		to)			// audio object to copy to
{
	Double		frompos = AUDIO_UNKNOWN_TIME;
	Double		topos = AUDIO_UNKNOWN_TIME;
	Double		limit = AUDIO_UNKNOWN_TIME;

	return (Copy(to, frompos, topos, limit));
}

// Default Copy out routine. Specify the destination audio object,
// and src/dest start offsets.  limit is either the time to copy or
// AUDIO_UNKNOWN_TIME to copy to eof or error.
// frompos and topos are updated with the final positions.
// limit is updated with the amount of data actually copied.
AudioError Audio::
Copy(
	Audio*		to,			// audio object to copy to
	Double&		frompos,
	Double&		topos,
	Double&		limit)
{
	Double		len;
	Double		svpos;
	AudioError	err;

	// If positions are Undefined, try to set them properly
	if (Undefined(frompos))
		frompos = ReadPosition();
	if (Undefined(topos))
		topos = to->WritePosition();

	svpos = frompos;
	do {
		// Calculate remaining copy size
		if (Undefined(limit)) {
			len = limit;
		} else {
			len = limit - (frompos - svpos);
			if (len < 0.)
				len = 0.;
		}
		// Copy one segment
		err = AsyncCopy(to, frompos, topos, len);
		if (!err) {
			switch (err.sys) {
			default:
			case 0:
				break;

			// XXX - What do we do with short writes?
			//	 This routine is meant to block until all the
			//	 data has been copied.  So copies to a pipe or
			//	 device should continue.  However, copies to a
			//	 buffer (or extent or list?) will never go any
			//	further.
			// For now, punt and return immediately.
			case AUDIO_COPY_SHORT_OUTPUT:
				goto outofloop;

			// If a zero-length transfer was requested, we're done
			case AUDIO_COPY_ZERO_LIMIT:
				goto outofloop;

			// If the input would block, we're done
			case AUDIO_COPY_SHORT_INPUT:
				goto outofloop;
			}
		}
	} while (err == AUDIO_SUCCESS);
outofloop:
	// Calculate total transfer count
	limit = frompos - svpos;

	// Declare victory if anything was copied
	if (limit > 0.)
		return (AUDIO_SUCCESS);
	return (err);
}

// Default Data Copy out routine. Like Copy(), but only does one segment.
// If either src or dest are set non-blocking, a partial transfer may occur.
// Returns AUDIO_SUCCESS on normal completion, regardless of how much data
// was actually transferred (err.sys: AUDIO_COPY_SHORT_INPUT if input would
// block;  AUDIO_COPY_ZERO_LIMIT if a zero-length copy was requested).
// Returns AUDIO_SUCCESS (err.sys: AUDIO_COPY_SHORT_OUTPUT) if more data was
// read than could be copied out (eg, if there was a short write to a
// non-blocking output).  Short writes result in the input pointer being
// backed up to the right place in the input stream.
// Returns AUDIO_EOF if input or output position beyond end-of-file.
//
// XXX - If the input cannot seek backwards, this routine will spin trying
//	 to finish writing all input data to the output.  We need to keep
//	 partial data in a state structure.
AudioError Audio::
AsyncCopy(
	Audio*		to,			// audio object to copy to
	Double&		frompos,
	Double&		topos,
	Double&		limit)
{
	caddr_t		bptr;
	size_t		bufsiz;
	size_t		lim;
	Double		svfrom;
	Double		svto;
	AudioBuffer*	tob;
	AudioHdr	tohdr;
	AudioError	err;

	// Validate basic arguments and state
	tohdr = to->GetHeader();
	if (err = tohdr.Validate())
		return (err);
	if (limit < 0.)
		return (RaiseError(AUDIO_ERR_BADARG));
	lim = (size_t)tohdr.Time_to_Bytes(limit);

	// If the destination is an AudioBuffer, we can copy more directly
	if (to->isBuffer()) {
		tob = (AudioBuffer*) to;

		// Get the buffer address at the starting offset
		bptr = (caddr_t)tob->GetAddress(topos);
		bufsiz = bptr - (caddr_t)tob->GetAddress();
		if ((bptr == NULL) || (tob->GetByteCount() <= bufsiz)) {
			limit = 0.;
			err = AUDIO_EOF;
			err.sys = AUDIO_COPY_OUTPUT_EOF;
			return (err);
		}
		bufsiz = tob->GetByteCount() - bufsiz;

		// Limit the data transfer by the limit argument
		if (!Undefined(limit) && (lim < bufsiz))
			bufsiz = lim;

		// Read the data directly into buffer
		(void) tohdr.Bytes_to_Bytes(bufsiz);
		err = ReadData((void*) bptr, bufsiz, frompos);
		limit = tohdr.Bytes_to_Time(bufsiz);
		topos += limit;
		tob->SetLength(topos);
		return (err);
	}

	// XXX - temporary bogus implementation
	// XXX - max transfer buf will be 2 seconds of data (1 sec for stereo)
	if (tohdr.channels < 2) {
		bufsiz = (size_t)tohdr.Time_to_Bytes(2.0);
	} else {
		bufsiz = (size_t)tohdr.Time_to_Bytes(1.0);
	}
	if (!Undefined(limit) && (lim < bufsiz))
		bufsiz = lim;

	limit = 0.;
	if ((bptr = new char[bufsiz]) == NULL)
		return (AUDIO_UNIXERROR);

	svfrom = frompos;
	err = ReadData((void*)bptr, bufsiz, frompos);
	if (!err) {
		svto = topos;
		lim = bufsiz;
		if (tohdr.Bytes_to_Bytes(bufsiz) != lim) {
			AUDIO_DEBUG((1,
			    "Read returned a fraction of a sample frame?!\n"));
			lim = bufsiz;
		}
		if (bufsiz > 0) {
			err = to->WriteData(bptr, bufsiz, topos);
			limit = topos - svto;

			// If the write was short, back up the input pointer
			if (bufsiz < lim) {
				lim = bufsiz;
				if (tohdr.Bytes_to_Bytes(bufsiz) != lim) {
					AUDIO_DEBUG((1,
		    "Write returned a fraction of a sample frame?!\n"));
				}
				frompos = svfrom + limit;
				if (!err)
					err.sys = AUDIO_COPY_SHORT_OUTPUT;
			}
		}
	}
	delete bptr;
	return (err);
}