view decoders/voc.c @ 474:c66080364dff

Most decoders now report total sample play time, now. Technically, this breaks binary compatibility with the 1.0 branch, since it extends the Sound_Sample struct, but most (all?) programs are just passing pointers allocated by SDL_sound around, and might be okay. Source-level compatibility is not broken...yet! :) --ryan. -------- Original Message -------- Subject: SDL_sound patch: Finding total length of time of sound file. Date: Sun, 26 Jan 2003 09:31:17 -0800 (PST) Hi Ryan, I am working with Eric Wing and helping him modify SDL_sound. AS part of our efforts in improving and enhancing SDL_sound, we like to submit this patch. We modified the codecs to find the total time of a sound file. Below is the explanation of the patch. The patch is appended as an attachment to this email. * MOTIVATION: We needed the ability to get the total play time of a sample (And we noticed that we're not the only ones). Since SDL_sound blocks direct access to the specific decoders, there is no way for a user to know this information short of decoding the whole thing. Because of this, we believe this will be a useful addition, even though the accuracy may not be perfect (subject to each decoder) or the information may not always be available. * CONTRIBUTORS: Wesley Leong (modified the majority of the codecs and verified the results) Eric Wing (showed everyone how to do modify codec, modified mikmod) Wang Lam (modified a handful of codecs, researched into specs and int overflow) Ahilan Anantha (modified a few codecs and helped with integer math) * GENERAL ISSUES: We chose the value to be milliseconds as an Sint32. Milliseconds because that's what Sound_Seek takes as a parameter and -1 to allow for instances/codecs where the value could not be determined. We are not sure if this is the final convention you want, so we are willing to work with you on this. We also expect the total_time field to be set on open and never again modified by SDL_sound. Users may access it directly much like the sample buffer and buffer_size. We thought about recomputing the time on DecodeAll, but since users may seek or decode small chunks first, not all the data may be there. So this is better done by the user. This may be good information to document. Currently, all the main codecs are implemented except for QuickTime.
author Ryan C. Gordon <icculus@icculus.org>
date Sat, 08 May 2004 08:19:50 +0000
parents cd46c97e58bc
children 3e705c9180e5
line wrap: on
line source

/*
 * SDL_sound -- An abstract sound format decoding API.
 * Copyright (C) 2001  Ryan C. Gordon.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * VOC decoder for SDL_sound.
 *
 * This driver handles Creative Labs VOC audio data...this is a legacy format,
 *  but there's some game ports that could make use of such a decoder. Plus,
 *  VOC is fairly straightforward to decode, so this is a more complex, but
 *  still palatable example of an SDL_sound decoder. Y'know, in case the
 *  RAW decoder didn't do it for you.  :)
 *
 * This code was ripped from a decoder I had written for SDL_mixer, which was
 *  largely ripped from sox v12.17.1's voc.c.
 *
 *    SDL_mixer: http://www.libsdl.org/projects/SDL_mixer/
 *    sox: http://www.freshmeat.net/projects/sox/
 *
 * Please see the file COPYING in the source's root directory.
 *
 *  This file written by Ryan C. Gordon. (icculus@clutteredmind.org)
 */

#if HAVE_CONFIG_H
#  include <config.h>
#endif

#ifdef SOUND_SUPPORTS_VOC

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "SDL_sound.h"

#define __SDL_SOUND_INTERNAL__
#include "SDL_sound_internal.h"

static int VOC_init(void);
static void VOC_quit(void);
static int VOC_open(Sound_Sample *sample, const char *ext);
static void VOC_close(Sound_Sample *sample);
static Uint32 VOC_read(Sound_Sample *sample);
static int VOC_rewind(Sound_Sample *sample);
static int VOC_seek(Sound_Sample *sample, Uint32 ms);

static const char *extensions_voc[] = { "VOC", NULL };
const Sound_DecoderFunctions __Sound_DecoderFunctions_VOC =
{
    {
        extensions_voc,
        "Creative Labs Voice format",
        "Ryan C. Gordon <icculus@clutteredmind.org>",
        "http://www.icculus.org/SDL_sound/"
    },

    VOC_init,       /*   init() method */
    VOC_quit,       /*   quit() method */
    VOC_open,       /*   open() method */
    VOC_close,      /*  close() method */
    VOC_read,       /*   read() method */
    VOC_rewind,     /* rewind() method */
    VOC_seek        /*   seek() method */
};


/* Private data for VOC file */
typedef struct vocstuff {
    Uint32  rest;           /* bytes remaining in current block */
    Uint32  rate;           /* rate code (byte) of this chunk */
    int     silent;         /* sound or silence? */
    Uint32  srate;          /* rate code (byte) of silence */
    Uint32  blockseek;	    /* start of current output block */
    Uint32  samples;	    /* number of samples output */
    Uint32  size;           /* word length of data */
    Uint8   channels;       /* number of sound channels */
    int     extended;       /* Has an extended block been read? */
    Uint32  bufpos;         /* byte position in internal->buffer. */
    Uint32  start_pos;      /* offset to seek to in stream when rewinding. */
    int     error;          /* error condition (as opposed to EOF). */
} vs_t;


/* Size field */ 
/* SJB: note that the 1st 3 are sometimes used as sizeof(type) */
#define ST_SIZE_BYTE     1
#define ST_SIZE_8BIT     1
#define ST_SIZE_WORD     2
#define ST_SIZE_16BIT    2
#define ST_SIZE_DWORD    4
#define ST_SIZE_32BIT    4
#define ST_SIZE_FLOAT    5
#define ST_SIZE_DOUBLE   6
#define ST_SIZE_IEEE     7   /* IEEE 80-bit floats. */

/* Style field */
#define ST_ENCODING_UNSIGNED  1 /* unsigned linear: Sound Blaster */
#define ST_ENCODING_SIGN2     2 /* signed linear 2's comp: Mac */
#define ST_ENCODING_ULAW      3 /* U-law signed logs: US telephony, SPARC */
#define ST_ENCODING_ALAW      4 /* A-law signed logs: non-US telephony */
#define ST_ENCODING_ADPCM     5 /* Compressed PCM */
#define ST_ENCODING_IMA_ADPCM 6 /* Compressed PCM */
#define ST_ENCODING_GSM       7 /* GSM 6.10 33-byte frame lossy compression */

#define VOC_TERM      0
#define VOC_DATA      1
#define VOC_CONT      2
#define VOC_SILENCE   3
#define VOC_MARKER    4
#define VOC_TEXT      5
#define VOC_LOOP      6
#define VOC_LOOPEND   7
#define VOC_EXTENDED  8
#define VOC_DATA_16   9


static int VOC_init(void)
{
    return(1);  /* always succeeds. */
} /* VOC_init */


static void VOC_quit(void)
{
    /* it's a no-op. */
} /* VOC_quit */


static __inline__ int voc_readbytes(SDL_RWops *src, vs_t *v, void *p, int size)
{
    if (SDL_RWread(src, p, size, 1) != 1)
    {
        v->error = 1;
        BAIL_MACRO("VOC: i/o error", 0);
    } /* if */

    return(1);
} /* voc_readbytes */


static __inline__ int voc_check_header(SDL_RWops *src)
{
    /* VOC magic header */
    Uint8  signature[20];  /* "Creative Voice File\032" */
    Uint16 datablockofs;
    vs_t v; /* dummy struct for voc_readbytes */

    if (!voc_readbytes(src, &v, signature, sizeof (signature)))
        return(0);

    if (memcmp(signature, "Creative Voice File\032", sizeof (signature)) != 0)
    {
        BAIL_MACRO("VOC: Wrong signature; not a VOC file.", 0);
    } /* if */

        /* get the offset where the first datablock is located */
    if (!voc_readbytes(src, &v, &datablockofs, sizeof (Uint16)))
        return(0);

    datablockofs = SDL_SwapLE16(datablockofs);

    if (SDL_RWseek(src, datablockofs, SEEK_SET) != datablockofs)
    {
        BAIL_MACRO("VOC: Failed to seek to data block.", 0);
    } /* if */

    return(1);  /* success! */
} /* voc_check_header */


/* Read next block header, save info, leave position at start of data */
static int voc_get_block(Sound_Sample *sample, vs_t *v)
{
    Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
    SDL_RWops *src = internal->rw;
    Uint8 bits24[3];
    Uint8 uc, block;
    Uint32 sblen;
    Uint16 new_rate_short;
    Uint32 new_rate_long;
    Uint8 trash[6];
    Uint16 period;
    Uint32 bytes_per_second;
    int i;

    v->silent = 0;
    while (v->rest == 0)
    {
        if (SDL_RWread(src, &block, sizeof (block), 1) != 1)
            return 1;  /* assume that's the end of the file. */

        if (block == VOC_TERM)
            return 1;

        if (SDL_RWread(src, bits24, sizeof (bits24), 1) != 1)
            return 1;  /* assume that's the end of the file. */
        
        /* Size is an 24-bit value. Ugh. */
        sblen = ( (bits24[0]) | (bits24[1] << 8) | (bits24[2] << 16) );

        switch(block)
        {
            case VOC_DATA:
                if (!voc_readbytes(src, v, &uc, sizeof (uc)))
                    return 0;

                /* When DATA block preceeded by an EXTENDED     */
                /* block, the DATA blocks rate value is invalid */
                if (!v->extended)
                {
                    BAIL_IF_MACRO(uc == 0, "VOC: Sample rate is zero?", 0);

                    if ((v->rate != -1) && (uc != v->rate))
                        BAIL_MACRO("VOC sample rate codes differ", 0);

                    v->rate = uc;
                    sample->actual.rate = 1000000.0/(256 - v->rate);
                    v->channels = 1;
                } /* if */

                if (!voc_readbytes(src, v, &uc, sizeof (uc)))
                    return(0);

                BAIL_IF_MACRO(uc != 0, "VOC: only supports 8-bit data", 0);

                v->extended = 0;
                v->rest = sblen - 2;
                v->size = ST_SIZE_BYTE;

                bytes_per_second = sample->actual.rate
                    * sample->actual.channels;
                sample->total_time += ( v->rest ) / bytes_per_second * 1000;
		sample->total_time += (v->rest % bytes_per_second) * 1000
		    / bytes_per_second;
                return 1;

            case VOC_DATA_16:
                if (!voc_readbytes(src, v, &new_rate_long, sizeof (Uint32)))
                    return 0;

                new_rate_long = SDL_SwapLE32(new_rate_long);
                BAIL_IF_MACRO(!new_rate_long, "VOC: Sample rate is zero?", 0);

                if ((v->rate != -1) && (new_rate_long != v->rate))
                    BAIL_MACRO("VOC: sample rate codes differ", 0);

                v->rate = new_rate_long;
                sample->actual.rate = new_rate_long;

                if (!voc_readbytes(src, v, &uc, sizeof (uc)))
                    return 0;

                switch (uc)
                {
                    case 8:  v->size = ST_SIZE_BYTE; break;
                    case 16: v->size = ST_SIZE_WORD; break;
                    default:
                        BAIL_MACRO("VOC: unknown data size", 0);
                } /* switch */

                if (!voc_readbytes(src, v, &v->channels, sizeof (Uint8)))
                    return 0;

                if (!voc_readbytes(src, v, trash, sizeof (Uint8) * 6))
                    return 0;
                v->rest = sblen - 12;

		bytes_per_second = ((v->size == ST_SIZE_WORD) ? (2) : (1)) *
		    sample->actual.rate * v->channels;
                sample->total_time += v->rest / bytes_per_second * 1000;
                sample->total_time += ( v->rest % bytes_per_second ) * 1000
		    / bytes_per_second;
                return 1;

            case VOC_CONT:
                v->rest = sblen;
                return 1;

            case VOC_SILENCE:
                if (!voc_readbytes(src, v, &period, sizeof (period)))
                    return 0;

                period = SDL_SwapLE16(period);

                if (!voc_readbytes(src, v, &uc, sizeof (uc)))
                    return 0;

                BAIL_IF_MACRO(uc == 0, "VOC: silence sample rate is zero", 0);

                /*
                 * Some silence-packed files have gratuitously
                 * different sample rate codes in silence.
                 * Adjust period.
                 */
                if ((v->rate != -1) && (uc != v->rate))
                    period = (period * (256 - uc))/(256 - v->rate);
                else
                    v->rate = uc;
                v->rest = period;
                v->silent = 1;

		sample->total_time += (period) / (v->rate) * 1000;
		sample->total_time += (period % v->rate) * 1000 / v->rate; 
                return 1;

            case VOC_LOOP:
            case VOC_LOOPEND:
                for(i = 0; i < sblen; i++)   /* skip repeat loops. */
                {
                   if (!voc_readbytes(src, v, trash, sizeof (Uint8)))
                        return 0;
                } /* for */
                break;

            case VOC_EXTENDED:
                /* An Extended block is followed by a data block */
                /* Set this byte so we know to use the rate      */
                /* value from the extended block and not the     */
                /* data block.                     */
                v->extended = 1;
                if (!voc_readbytes(src, v, &new_rate_short, sizeof (Uint16)))
                    return 0;

                new_rate_short = SDL_SwapLE16(new_rate_short);
                BAIL_IF_MACRO(!new_rate_short, "VOC: sample rate is zero", 0);

                if ((v->rate != -1) && (new_rate_short != v->rate))
                   BAIL_MACRO("VOC: sample rate codes differ", 0);

                v->rate = new_rate_short;

                if (!voc_readbytes(src, v, &uc, sizeof (uc)))
                    return 0;

                BAIL_IF_MACRO(uc != 0, "VOC: only supports 8-bit data", 0);

                if (!voc_readbytes(src, v, &uc, sizeof (uc)))
                    return 0;

                if (uc)
                    sample->actual.channels = 2;  /* Stereo */

                /* Needed number of channels before finishing
                   compute for rate */
                sample->actual.rate =
                     (256000000L/(65536L - v->rate)) / sample->actual.channels;
                /* An extended block must be followed by a data */
                /* block to be valid so loop back to top so it  */
                /* can be grabed.                */
                continue;

            case VOC_MARKER:
                if (!voc_readbytes(src, v, trash, sizeof (Uint8) * 2))
                    return 0;

                /* Falling! Falling! */

            default:  /* text block or other krapola. */
                for(i = 0; i < sblen; i++)   /* skip repeat loops. */
                {
                   if (!voc_readbytes(src, v, trash, sizeof (Uint8)))
                        return 0;
                } /* for */

                if (block == VOC_TEXT)
                    continue;    /* get next block */
        } /* switch */
    } /* while */

    return 1;
} /* voc_get_block */


static int voc_read_waveform(Sound_Sample *sample, int fill_buf, Uint32 max)
{
    Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
    SDL_RWops *src = internal->rw;
    vs_t *v = (vs_t *) internal->decoder_private;
    int done = 0;
    Uint8 silence = 0x80;
    Uint8 *buf = internal->buffer;

    if (v->rest == 0)
    {
        if (!voc_get_block(sample, v))
            return 0;
    } /* if */

    if (v->rest == 0)
        return 0;

    max = (v->rest < max) ? v->rest : max;

    if (v->silent)
    {
        if (v->size == ST_SIZE_WORD)
            silence = 0x00;

        /* Fill in silence */
        if (fill_buf)
            memset(buf + v->bufpos, silence, max);

        done = max;
        v->rest -= done;
    } /* if */

    else
    {
        if (fill_buf)
        {
            done = SDL_RWread(src, buf + v->bufpos, 1, max);
            if (done < max)
            {
                __Sound_SetError("VOC: i/o error");
                sample->flags |= SOUND_SAMPLEFLAG_ERROR;
            } /* if */
        } /* if */

        else
        {
            int cur, rc;
            cur = SDL_RWtell(src);
            if (cur >= 0)
            {
                rc = SDL_RWseek(src, max, SEEK_CUR);
                if (rc >= 0)
                    done = rc - cur;
                else
                {
                    __Sound_SetError("VOC: seek error");
                    sample->flags |= SOUND_SAMPLEFLAG_ERROR;
                } /* else */
            } /* if */
        } /* else */

        v->rest -= done;
        v->bufpos += done;
    } /* else */

    return(done);
} /* voc_read_waveform */


static int VOC_open(Sound_Sample *sample, const char *ext)
{
    Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
    vs_t *v = NULL;

    if (!voc_check_header(internal->rw))
        return(0);

    v = (vs_t *) malloc(sizeof (vs_t));
    BAIL_IF_MACRO(v == NULL, ERR_OUT_OF_MEMORY, 0);
    memset(v, '\0', sizeof (vs_t));

    v->start_pos = SDL_RWtell(internal->rw);
    v->rate = -1;
    if (!voc_get_block(sample, v))
    {
        free(v);
        return(0);
    } /* if */

    if (v->rate == -1)
    {
        free(v);
        BAIL_MACRO("VOC: data had no sound!", 0);
    } /* if */

    SNDDBG(("VOC: Accepting data stream.\n"));
    sample->actual.format = (v->size == ST_SIZE_WORD) ? AUDIO_S16LSB:AUDIO_U8;
    sample->actual.channels = v->channels;
    sample->flags = SOUND_SAMPLEFLAG_CANSEEK;
    internal->decoder_private = v;
    return(1);
} /* VOC_open */


static void VOC_close(Sound_Sample *sample)
{
    Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
    free(internal->decoder_private);
} /* VOC_close */


static Uint32 VOC_read(Sound_Sample *sample)
{
    Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
    vs_t *v = (vs_t *) internal->decoder_private;

    v->bufpos = 0;
    while (v->bufpos < internal->buffer_size)
    {
        Uint32 rc = voc_read_waveform(sample, 1, internal->buffer_size);
        if (rc == 0)
        {
            sample->flags |= (v->error) ? 
                                 SOUND_SAMPLEFLAG_ERROR :
                                 SOUND_SAMPLEFLAG_EOF;
            break;
        } /* if */

        if (!voc_get_block(sample, v))
        {
            sample->flags |= (v->error) ? 
                                 SOUND_SAMPLEFLAG_ERROR :
                                 SOUND_SAMPLEFLAG_EOF;
            break;
        } /* if */
    } /* while */

    return(v->bufpos);
} /* VOC_read */


static int VOC_rewind(Sound_Sample *sample)
{
    Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
    vs_t *v = (vs_t *) internal->decoder_private;
    int rc = SDL_RWseek(internal->rw, v->start_pos, SEEK_SET);
    BAIL_IF_MACRO(rc != v->start_pos, ERR_IO_ERROR, 0);
    v->rest = 0;
    return(1);
} /* VOC_rewind */


static int VOC_seek(Sound_Sample *sample, Uint32 ms)
{
    /*
     * VOCs don't lend themselves well to seeking, since you have to
     *  parse each section, which is an arbitrary size. The best we can do
     *  is rewind, set a flag saying not to write the waveforms to a buffer,
     *  and decode to the point that we want. Ugh. Fortunately, there's
     *  really no such thing as a large VOC, due to the era and hardware that
     *  spawned them, so even though this is inefficient, this is still a
     *  relatively fast operation in most cases.
     */

    Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
    vs_t *v = (vs_t *) internal->decoder_private;
    int offset = __Sound_convertMsToBytePos(&sample->actual, ms);
    int origpos = SDL_RWtell(internal->rw);
    int origrest = v->rest;

    BAIL_IF_MACRO(!VOC_rewind(sample), NULL, 0);

    v->bufpos = 0;

    while (offset > 0)
    {
        Uint32 rc = voc_read_waveform(sample, 0, offset);
        if ( (rc == 0) || (!voc_get_block(sample, v)) )
        {
            SDL_RWseek(internal->rw, origpos, SEEK_SET);
            v->rest = origrest;
            return(0);
        } /* if */

        offset -= rc;
    } /* while */

    return(1);
} /* VOC_seek */

#endif /* SOUND_SUPPORTS_VOC */

/* end of voc.c ... */