[sdlsound] SDL_sound v2's internal mixer format...

Tyler Montbriand tsm at accesscomm.ca
Sun Aug 8 17:34:27 EDT 2004


On Saturday 07 August 2004 01:06, Ryan C. Gordon wrote:
> I've been thinking about this, and it occurs to me that most games don't
> have a huge problem with audio clipping at mix time, and if they do, no
> one cares, so forcing everything to float32 isn't really worth it.
I'm glad we agree.  :)  I'd still worry about clipping, though.  Most integer 
mixers can ignore it because they have strict volume ranges and fixed numbers 
of channels, but this thing won't.

> My thought is that we should use signed 16-bit ints on all platforms if
> possible, regardless of the presence of an FPU.
Again I'd suggest mixing 16-bit samples in a 32-bit mixer.  The extra legroom 
and accuracy is cheap - I can't name a SDL-supporting system that doesn't use 
a >=32-bit processor.  You can pile 32,000 16-bit samples on before it risks 
clipping, or 128 24-bit ones.

> I also think callbacks are on their own and should deal with whatever
> format they are fed (which allows for later expansion to other formats
> and prevents the need for conversion just for the callback).
>
> Kinda bothers me that it has to be this way, since it adds complexity to
> both the mixer and the application that wants to use callbacks, but I
> really can't see a better solution to this.
If it's mixing in strict 16 or 32 bit, wouldn't it always be the same format?  
Or will the Sound_Samples themselves have callbacks?

Anyway, I've often thought that SDL audio callbacks are so messy because they 
lack good structures and routines to use in them...  the repeated typecasting 
makes it more like Lisp than C, and I'm always cobbling together the same 
little conversion routines.

Little things can make a lot of difference.  I love C unions:

  /* audioptr.c, Tyler Montbriand 2004.  Free as in free beer. */
  #include <stdio.h>
  #include <SDL/SDL_types.h>

  typedef union audioptr 
  {
    Sint16     *s16_mono;       /*   s16_mono[sample]            */
    Sint16    (*s16_stereo)[2]; /* s16_stereo[sample][channel]   */
    Uint8      *u8_mono;        /*    u8_mono[sample]            */
    Uint8     (*u8_stereo)[2];  /*  u8_stereo[sample][channel]   */
                                /*        ... etc ...            */
    const void *cv;             /* Can set with any pointer type */
  } audioptr;

  #define LEFT 0  /* Is this backwards?  I'm not sure. */
  #define RiGHT 1

  int main()
  {
    Sint16 data[]={1,-1,2,-2,3,-3,4,-4}; /*  raw "audio" data  */
    audioptr ptr={data};                 /* audio data pointer */

    /* Print left channel data */
    printf("LEFT:  %+d %+d %+d %+d\n", ptr.s16_stereo[0][ LEFT],
                                       ptr.s16_stereo[1][ LEFT],
                                       ptr.s16_stereo[2][ LEFT],
                                       ptr.s16_stereo[3][ LEFT]);
    /* Print right channel data */
    printf("RIGHT: %+d %+d %+d %+d\n", ptr.s16_stereo[0][RIGHT],
                                       ptr.s16_stereo[1][RIGHT],
                                       ptr.s16_stereo[2][RIGHT],
                                       ptr.s16_stereo[3][RIGHT]);
    /* Show raw interleaved audio */
    printf("RAW:   %+d %+d %+d %+d\n", ptr.s16_mono[0],
                                       ptr.s16_mono[1],
                                       ptr.s16_mono[2],
                                       ptr.s16_mono[3]);
    return(0);
  }
  /*******************************************************************/
Didn't know you could make pointers to arrays, hooray for the almighty google 
groups.  Anyway, it prints:

  LEFT:  +1 +2 +3 +4
  RIGHT: -1 -2 -3 -4
  RAW:   +1 -1 +2 -2

The union/array/pointer...thing seperates the channels for you.  Passing one 
for stream data would let the programmer do things like:

  stream.s16_stereo[sample][RIGHT]

instead of:

  ((Sint16 *)stream)[(sample*2)+1]

The biggest aggravation I have writing callbacks is units and limits checking.  
Is that number in bits or bytes or samples or sloods?  A tricky mistake can 
cause it to read past the end of the buffer, and if it really borks it might 
read backwards!  We can make macros to convert everything into samples and do 
limit checking.

  /* audio_size.c, Tyler Montbriand 2004.  Free, as in free beer. */
  #include <stdio.h>
  #include <SDL/SDL.h>
  #include <SDL/SDL_sound.h>

  /**
   * Ever notice that the last 8 bits of an audio format flag are
   * it's bitsize?  Now you know.  Returns the bytes per sample of
   * a Sound_AudioInfo struct.
   */
  #define Sound_BPS(info) (((info.format&0xff)/8)*info.channels)

  /* Returns the # of samples in a Sound_Sample *'s buffer. */
  #define Sound_Samples(smp) (smp->buffer_size/Sound_BPS(smp->desired))

  /**
   *  Takes a Sound_Sample * and a sample position.  Returns 1 if the
   *  position is valid, 0 if not.
   */
  #define Sound_InLimits(smp,pos) ((pos >= 0)&&(pos < Sound_Samples(smp)))

  /* Structures to test with Sound_BPS() */
  Sound_AudioInfo info[]={ {AUDIO_S16,2,44100},
                           {AUDIO_S16,1,44100},
                           {AUDIO_S8 ,2,44100},
                           {AUDIO_S8 ,1,44100} };
  /* Titles to go along with them */
  const char *titles[]=  {"16-bit stereo BPS:",
                          "  16-bit mono BPS:",
                          " 8-bit stereo BPS:",
                          "   8-bit mono BPS:",
                           NULL                };
  /* Values Sound_BPS() should give */
  const int rightvalues[]={ 4, 2, 2, 1 };

  int main(int argc, char *argv[])
  {
    int n,retval=1;
    Sound_Sample *smp=NULL;

    if(SDL_Init(0)<0)  goto the_end; /* Initialize SDL       */
    if(Sound_Init()<0) goto the_end; /* Initialize SDL_sound */

    for(n=0; titles[n]!=NULL; n++) /* Test Sound_BPS */
    {
      if(Sound_BPS(info[n])==rightvalues[n])
        printf("[OK] %s %d\n",titles[n],Sound_BPS(info[n]));
      else
      {
        printf("[!!] %s %d\n",titles[n],Sound_BPS(info[n]));
        goto the_end;
      }
    }

    /* Load sample from file */
    printf("\nOpening %s\n",argv[1]);
    smp=Sound_NewSampleFromFile(argv[1],NULL,4096);
    if(smp==NULL)
    {
      printf("Couldn't open %s\n",argv[1]);
      goto the_end;
    }

    printf("%s opened as %p\n",argv[1],smp);
    Sound_DecodeAll(smp);

    /* Print statistics */
    printf("  Bytes: %d\n",smp->buffer_size);
    printf("  Channels: %d\n",smp->desired.channels);
    printf("  Bytes Per Sample: %d\n",Sound_BPS(smp->desired));
    printf("  Samples: %d\n\n",Sound_Samples(smp));

    printf("Testing limits at both ends of boundary.\n");
    printf("  Sound_InLimits(%p,-1):\t0 vs %d\n",
           smp,Sound_InLimits(smp,-1));
    printf("  Sound_InLimits(%p,0):\t1 vs %d\n",
           smp,Sound_InLimits(smp,0));
    printf("  Sound_InLimits(%p,%d):\t0 vs %d\n",
           smp,Sound_Samples(smp),
           Sound_InLimits(smp,Sound_Samples(smp)));
    printf("  Sound_InLimits(%p,%d-1):\t1 vs %d\n",
           smp,Sound_Samples(smp),
           Sound_InLimits(smp,Sound_Samples(smp)-1));

    Sound_FreeSample(smp);
    retval=0;
  the_end:
    Sound_Quit();
    SDL_Quit();
    return(retval);
  }
  /*******************************************************************/
It prints:

  [OK] 16-bit stereo BPS: 4
  [OK]   16-bit mono BPS: 2
  [OK]  8-bit stereo BPS: 2
  [OK]    8-bit mono BPS: 1

  Opening dotmatrix3.wav
  dotmatrix3.wav opened as 0x515890
    Bytes: 5526
    Channels: 1
    Bytes Per Sample: 1
    Samples: 5526

  Testing limits at both ends of boundary.
    Sound_InLimits(0x515890,-1):  0 vs 0
    Sound_InLimits(0x515890,0):   1 vs 1
    Sound_InLimits(0x515890,5526):        0 vs 0
    Sound_InLimits(0x515890,5526-1):      1 vs 1

If you're not bored out of your skulls by this point, a beef I've always had 
with the callbacks in SDL and SDL_mixer is there's no mechanism to retrieve 
the audio format from the callback.  You either have to plunk it in a global 
variable or cram it in userdata.  I think it ought to be there, ergo, this is 
the sort of callback I'd like to see:

  void callback(void *udata, audioptr stream, const Sound_Sample *smp);

stream is that weird pointer union.  smp gives you the format and buffer size, 
and the const prevents people from doing silly things like rewinding.  Here's 
how I'd imagine the code of a callback:

  /* Sets the buffer to silence */
  void silence(void *udata, audioptr stream, const Sound_Sample *smp)
  {
    int n,samples=Sound_Samples(smp);

    if(udata!=NULL)
    { /* Do something with user data */ }

    if(smp->desired.channels==2)
      switch(smp->desired.format) /* Stereo formats */
      {
      case AUDIO_U8:
        for(n=0; n<samples; n++)
        { stream.u8_stereo[n][RIGHT]=127;
          stream.u8_stereo[n][ LEFT]=127;     }
        break;
      case AUDIO_S8:
        for(n=0; n<samples; n++)
        { stream.s8_stereo[n][RIGHT]=0;
          stream.s8_stereo[n][ LEFT]=0;       }
        break;
      case AUDIO_U16:
        for(n=0; n<samples; n++)
        { stream.u16_stereo[n][RIGHT]=32767;
          stream.u16_stereo[n][ LEFT]=32767;  }
        break;
      case AUDIO_S16:
        for(n=0; n<samples; n++)
        { stream.s16_stereo[n][RIGHT]=0;
          stream.s16_stereo[n][ LEFT]=0;      }
        break;
      default:
        fprintf(stderr,"Unknown format\n");
        break;
      }
    else
      switch(smp->desired.format) /* mono formats */
      {
      case AUDIO_U8:
        for(n=0; n<samples; n++)
          stream.u8_mono[n]=127;
        break;
      case AUDIO_S8:
        for(n=0; n<samples; n++)
          stream.s8_mono[n]=0;
        break;
      case AUDIO_U16:
        for(n=0; n<samples; n++)
          stream.u16_mono[n]=32767;
        break;
      case AUDIO_S16:
        for(n=0; n<samples; n++)
          stream.s16_mono[n]=0;
        break;
      default:
        fprintf(stderr,"Unknown format\n");
        break;
      }
  }

Not terribly efficient, but it's clear enough.  And if the callbacks get fed 
only 32-bit data, the size would be quartered.

What do you think?




More information about the sdlsound mailing list