OpenSolaris_b135/cmd/audio/utilities/AudioBuffer.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) 1992-2001 by Sun Microsystems, Inc.
 * All rights reserved.
 */

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

#include <stdlib.h>
#include <memory.h>
#include "../include/AudioDebug.h"
#include "../include/AudioBuffer.h"
#include "../include/zmalloc.h"

// class AudioBuffer methods

// Constructor with optional hdr, size, and name arguments
AudioBuffer::
AudioBuffer(
	double		len,			// buffer length, in seconds
	const char	*local_name):			// name
	AudioStream(local_name), buflen(len), bufaddr(0), zflag(0), bufsize(0)
{
}

// Destructor
AudioBuffer::
~AudioBuffer()
{
	(void) SetSize(0.);		// deallocate the buffer
}

// XXX - the following functions are good candidates for inlining

// Return TRUE if the stream is 'open'
Boolean AudioBuffer::
opened() const
{
	// A buffer is open if it is allocated and has a valid header
	return (hdrset() && (GetAddress() != 0));
}

#define	MIN_ZBUFFER	(8192 * 10)	// only for large buffers

// Allocate buffer.  Size and header must be set.
AudioError AudioBuffer::
alloc()
{
	long		size;
	size_t		cnt;
	unsigned int	ncpy;
	void*		tmpbuf;

	// this is going to be the size we're setting the buffer
	// to (buflen field). it's set by calling SetSize().
	size = GetHeader().Time_to_Bytes(GetSize());

	// this is actual current size, in bytes, of the allocated
	// buffer (the bufsize field).
	cnt = GetByteCount();

	AUDIO_DEBUG((5, "%d: AudioBuffer::alloc - change from %d to %d bytes\n",
	    getid(), cnt, size));

	bufsize = 0;

	if (size == 0) {
		// Zero size deletes the buffer
		if (bufaddr != 0) {
			if (zflag != 0) {
				AUDIO_DEBUG((5,
			    "%d: AudioBuffer::alloc - zfree mmapped buffer\n",
				    getid()));
				(void) zfree((char *)bufaddr);
			} else {
				AUDIO_DEBUG((5,
			    "%d: AudioBuffer::alloc - free malloc'd buffer\n",
				    getid()));
				(void) free((char *)bufaddr);
			}
			zflag = 0;
		}
		bufaddr = 0;

	} else if (size < 0) {
		// Ridiculous size
		AUDIO_DEBUG((5, "%d: AudioBuffer::alloc - bad size\n",
		    getid()));
		return (RaiseError(AUDIO_ERR_BADARG));

	} else if (bufaddr == 0) {
		// Allocate a new buffer
		if (size > MIN_ZBUFFER) {
			AUDIO_DEBUG((5,
			    "%d: AudioBuffer::alloc - zmalloc new buffer\n",
			    getid()));
			bufaddr = (void*) zmalloc((unsigned int)size);
			zflag = 1;
		} else {
			AUDIO_DEBUG((5,
			    "%d: AudioBuffer::alloc - malloc new buffer\n",
			    getid()));
			bufaddr = (void*) malloc((unsigned int)size);
			zflag = 0;
		}
		if (bufaddr == 0) {
			AUDIO_DEBUG((5,
			    "%d: AudioBuffer::alloc - buffer alloc failed\n",
			    getid()));
			return (RaiseError(AUDIO_UNIXERROR));
		}
	} else {
		// A buffer was already allocated.
		// Change its size, preserving as much data as possible.
		if ((cnt <= MIN_ZBUFFER) && (size <= MIN_ZBUFFER) &&
		    (zflag == 0)) {
			AUDIO_DEBUG((5,
			    "%d: AudioBuffer::alloc - realloc to change size\n",
			    getid()));
			bufaddr = (void*)
			    realloc((char *)bufaddr, (unsigned int)size);
		} else {
			AUDIO_DEBUG((5,
			    "%d: AudioBuffer::alloc - zmalloc new buffer\n",
			    getid()));
			tmpbuf = bufaddr;
			bufaddr = (void*) zmalloc((unsigned int)size);

			// copy over as much of the old data as will fit
			if (bufaddr != 0) {
				ncpy = (cnt < size) ? (unsigned int)cnt :
					(unsigned int)size;

				AUDIO_DEBUG((5,
			    "%d: AudioBuffer::alloc - trasnfer %d bytes\n",
				    getid(), ncpy));
				(void) memcpy(bufaddr, tmpbuf, ncpy);
			}
			if ((cnt > MIN_ZBUFFER) && (zflag != 0)) {
				AUDIO_DEBUG((5,
			    "%d: AudioBuffer::alloc - zfree old buffer\n",
				    getid()));
				(void) zfree((char *)tmpbuf);
			} else {
				AUDIO_DEBUG((5,
			    "%d: AudioBuffer::alloc - free old buffer\n",
				    getid()));
				(void) free((char *)tmpbuf);
			}
			zflag = 1;
		}
		if (bufaddr == 0) {
			return (RaiseError(AUDIO_UNIXERROR));
		}
	}
	bufsize = (size_t)size;
	return (AUDIO_SUCCESS);
}


// Return the buffer address
void* AudioBuffer::
GetAddress() const
{
	return (GetAddress(0.));
}

// Return the buffer address at a given time offset
// Returns NULL if no buffer, or the position is not within the buffer.
void* AudioBuffer::
GetAddress(
	Double		pos) const
{
	char		*addr;
	AudioHdr	hdr_local;
	AudioHdr(AudioBuffer::*hfunc)()const;

	addr = (char *)bufaddr;
	if ((addr == 0) || (pos < 0.) || (pos >= buflen))
		return (NULL);

	// If no offset, it's ok if the header hasn't been set yet
	if (pos == 0.)
		return ((void*) addr);

	// Get the header and make sure it's valid
	// This convoluted hfunc works around non-const function problems
	hfunc = (AudioHdr(AudioBuffer::*)() const)&AudioBuffer::GetHeader;
	hdr_local = (this->*hfunc)();
	if (hdr_local.Validate())
		return (NULL);
	addr += hdr_local.Time_to_Bytes(pos);

	// One more validation, to be paranoid before handing out this address
	if (addr >= ((char *)bufaddr + bufsize))
		return (NULL);
	return ((void*) addr);
}

// Return the buffer size, in bytes
// (as opposed to 'length' which indicates how much data is in the buffer)
size_t AudioBuffer::
GetByteCount() const
{
	return (bufsize);
}

// Return the buffer size, in seconds
// (as opposed to 'length' which indicates how much data is in the buffer)
Double AudioBuffer::
GetSize() const
{
	return (buflen);
}

// Set the buffer size, allocating the buffer as necessary
AudioError AudioBuffer::
SetSize(
	Double		len)			// new size, in seconds
{
	// If no change in size, do nothing
	if (len == buflen)
		return (AUDIO_SUCCESS);

	// If header not set, store the size for later
	buflen = len;
	if (!hdrset()) {
		return (AUDIO_SUCCESS);
	}

	// If shrinking buffer, note this
	if (buflen < GetLength())
		SetLength(buflen);
	return (alloc());
}

// Set the data header
// If no buffer allocated, allocate one now (if size is set).
// If buffer allocated, fiddle the sizes to account for new header type.
AudioError AudioBuffer::
SetHeader(
	const AudioHdr& h)			// header to copy
{
	AudioError	err;

	// Validate, then update the header
	err = h.Validate();
	if (err)
		return (RaiseError(err));
	(void) AudioStream::updateheader(h);

	// If no size set, done for now
	if (buflen == 0.)
		return (AUDIO_SUCCESS);

	// If no buffer allocated, allocate one now
	if (GetAddress() == 0)
		return (alloc());

	// If buffer allocated, change size to match new header
	buflen = h.Bytes_to_Time(GetByteCount());
	return (AUDIO_SUCCESS);
}

// Set the buffer length (ie, the amount of data written to the buffer)
void AudioBuffer::
SetLength(
	Double		len)			// new length
{
	if (!hdrset() || (len < 0.))		// no-op if not ready
		return;
	if (!opened() && (len > 0.))
		return;

	if (Undefined(len) || (len > GetSize())) {
		// Limit to the size of the buffer
		setlength(GetSize());
	} else {
		setlength(len);
	}
}

// Copy data from local buffer into specified buffer.
// No data format translation takes place.
// The object's read position is not updated.
AudioError AudioBuffer::
ReadData(
	void*		buf,		// destination buffer address
	size_t&		len,		// buffer length (updated)
	Double&		pos)		// start position (updated)
{
	off_t		resid;
	off_t		cnt;
	off_t		offset;
	AudioError	err;

	// Copy length, zero return value
	cnt = (off_t)len;
	len = 0;

	// Cannot read if buffer or header not valid
	if (!opened())
		return (RaiseError(AUDIO_ERR_NOEFFECT));

	// Position must be valid
	if ((pos < 0.) || (cnt < 0))
		return (RaiseError(AUDIO_ERR_BADARG));

	// If the starting offset is at or beyond EOF, return eof flag
	if (pos >= GetLength()) {
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_INPUT_EOF;
		return (err);
	}

	// Limit transfer to remaining room in buffer
	offset = GetHeader().Time_to_Bytes(pos);
	resid = GetHeader().Time_to_Bytes(GetLength()) - offset;
	if (resid <= 0) {
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_INPUT_EOF;
		return (err);
	}
	if (cnt > resid)
		cnt = resid;

	// Fix the alignment to make sure we're not splitting frames
	err = AUDIO_SUCCESS;
	if (GetHeader().Bytes_to_Bytes(cnt) > 0) {
		// Copy as much data as possible
		memcpy((char *)buf, (char *)((off_t)GetAddress() + offset),
		    (int)cnt);
	} else {
		err.sys = AUDIO_COPY_ZERO_LIMIT;
	}

	// Return the updated transfer size and position
	len = (size_t)cnt;
	pos = GetHeader().Bytes_to_Time(offset + cnt);


	// Check to see if the endian is right.
	coerceEndian((unsigned char *)buf, len, localByteOrder());

	return (err);
}

// Copy data to local buffer from specified buffer.
// No data format translation takes place.
// The object's write position is not updated.
AudioError AudioBuffer::
WriteData(
	void*		buf,		// source buffer address
	size_t&		len,		// buffer length (updated)
	Double&		pos)		// start position (updated)
{
	off_t		resid;
	off_t		cnt;
	off_t		offset;
	AudioError	err;

	// Copy length, zero return value
	cnt = (off_t)len;
	len = 0;

	// Cannot write if buffer or header not valid
	if (!opened())
		return (RaiseError(AUDIO_ERR_NOEFFECT));

	// Position must be valid
	if ((pos < 0.) || (cnt < 0))
		return (RaiseError(AUDIO_ERR_BADARG));

	// If the starting offset beyond end of buffer, return short write flag
	if (pos >= GetSize()) {
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_OUTPUT_EOF;
		return (err);
	}

	// Limit transfer to remaining room in buffer
	offset = GetHeader().Time_to_Bytes(pos);
	resid = (off_t)bufsize - offset;
	if (resid <= 0) {
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_OUTPUT_EOF;
		return (err);
	}
	if (cnt > resid)
		cnt = resid;

	// Fix the alignment to make sure we're not splitting frames
	err = AUDIO_SUCCESS;
	if (GetHeader().Bytes_to_Bytes(cnt) > 0) {
		// Copy as much data as possible
		memcpy((char *)((off_t)GetAddress() + offset), (char *)buf,
		    (int)cnt);
	} else {
		err.sys = AUDIO_COPY_ZERO_LIMIT;
	}

	// Return the updated transfer size and position
	len = (size_t)cnt;
	pos = GetHeader().Bytes_to_Time(offset + cnt);

	// The end of a write to a buffer always becomes the buffer EOF
	setlength(pos);
	return (err);
}

// AppendData is just like WriteData, except that it guarantees to extend
// the buffer if it is not big enough.
// The object's write position is not updated.
AudioError AudioBuffer::
AppendData(
	void*		buf,		// source buffer address
	size_t&		len,		// buffer length (updated)
	Double&		pos)		// start position (updated)
{
	Double		local_length;
	AudioError	err;

	// Cannot write if header not valid
	if (!hdrset())
		return (RaiseError(AUDIO_ERR_NOEFFECT));

	// Position must be valid
	if (pos < 0.)
		return (RaiseError(AUDIO_ERR_BADARG));

	// If the ending offset is beyond end of buffer, extend it
	local_length = pos + GetHeader().Bytes_to_Time(len);
	if (local_length > GetSize()) {
		if (err = SetSize(local_length))
			return (err);
	}
	return (WriteData(buf, len, pos));
}

// Copy routine to copy direct to destination
AudioError AudioBuffer::
AsyncCopy(
	Audio*		to,			// audio object to copy to
	Double&		frompos,
	Double&		topos,
	Double&		limit)
{
	caddr_t		bptr;
	size_t		cnt;
	size_t		svcnt;
	Double		svfrom;
	Double		svto;
	Double		lim;
	AudioHdr	tohdr;
	AudioError	err;

	// Cannot write if buffer or header not valid
	if (!opened())
		return (RaiseError(AUDIO_ERR_NOEFFECT));

	tohdr = to->GetHeader();
	if (limit < 0.)
		return (RaiseError(AUDIO_ERR_BADARG));

	// Get maximum possible copy length
	svfrom = GetLength();
	if (frompos >= svfrom) {
		limit = 0.;
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_INPUT_EOF;
		return (err);
	}
	lim = svfrom - frompos;
	if (!Undefined(limit) && (limit < lim))
		lim = limit;

	limit = 0.;

	bptr = (caddr_t)GetAddress(frompos);
	if (bptr == 0) {
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_INPUT_EOF;
		return (err);
	}
	cnt = (size_t)GetHeader().Time_to_Bytes(lim);
	if (cnt == 0) {
		err = AUDIO_SUCCESS;
		err.sys = AUDIO_COPY_ZERO_LIMIT;
		return (err);
	}

	// Add a bunch of paranoid checks
	svcnt = (size_t)GetAddress() + (size_t)GetByteCount();
	if ((bptr + cnt) > (caddr_t)svcnt) {
		// re-adjust cnt so it reads up to the end of file
		cnt = (size_t)((caddr_t)svcnt - bptr);
	}
	if (GetHeader().Bytes_to_Bytes(cnt) == 0) {
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_INPUT_EOF;
		return (err);
	}

	// Write the data to the destination and update pointers/ctrs
	svfrom = frompos;
	svto = topos;
	svcnt = cnt;
	err = to->WriteData(bptr, cnt, topos);
	limit = topos - svto;
	frompos = svfrom + limit;

	// Report short writes
	if (!err && (cnt < svcnt)) {
		err.sys = AUDIO_COPY_SHORT_OUTPUT;
	}
	return (err);
}