root/usr/src/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.
 */

#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), zflag(0), bufsize(0), bufaddr(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()) {
                err = SetSize(local_length);
                if (err != AUDIO_SUCCESS)
                        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);
}