Bug 21073

Summary: PCM sound driver silently accepts improper parameters of SNDCTL_DSP_SETFMT
Product: Base System Reporter: Andre Albsmeier <Andre.Albsmeier>
Component: kernAssignee: cg
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 4.1-STABLE   
Hardware: Any   
OS: Any   

Description Andre Albsmeier 2000-09-06 08:20:00 UTC
When setting a sound format with SNDCTL_DSP_SETFMT, the system call does not
tell us if an improper format was set.

How-To-Repeat: 
The following program demonstrates the error:

#include <stdio.h>   
#include <ctype.h>
#include <string.h>
#include <machine/soundcard.h>
#include <sys/time.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main( ) {

  int bits;
  int fd;

  if( (fd = open("/dev/dsp", O_RDONLY)) < 0 ) {
    fprintf( stderr, "Can't open /dev/dsp for read\n" );
    exit( 1 );
  }

  if( ioctl(fd, SNDCTL_DSP_GETFMTS, &bits) < 0 ) {
    fprintf( stderr, "SNDCTL_DSP_GETFMTS error\n" );
    exit( 1 );
  }
  
  printf( "GETFMTS: 0x%X\n", bits );
    
  bits = 0x10;
  if( ioctl(fd, SNDCTL_DSP_SETFMT, &bits) < 0 ) {
    fprintf( stderr, "SNDCTL_DSP_SETFMT error\n" );
    exit( 1 );
  }

  printf( "After setting 0x10 with SETFMT: 0x%X\n", bits );

  return( 0 );
}


andre@bali:~>./sndcheck 
GETFMTS: 0x10000008
After setting 0x10 with SETFMT: 0x10

So the AWE64 only supports AFMT_U8 in STEREO. Now we set AFMT_S16_LE
but there is no error returned neither was the bits variable modified
as it was the case in voxware.
Comment 1 Sheldon Hearn freebsd_committer freebsd_triage 2000-09-06 12:19:11 UTC
Responsible Changed
From-To: freebsd-bugs->cg

Over to maintainer.
Comment 2 gandalf 2000-09-06 15:03:50 UTC
SNDCTL_DSP_GETFMTS returns the hardware formats supported.
SNDCTL_DSP_SETFMT is allowed to set other formats using software
translation, and does so.

    -cg
Comment 3 Andre Albsmeier 2000-09-06 15:13:06 UTC
[CC'ed to Randall Hopper <aa8vb@nc.rr.com> as well since we
 both are currently lost with this]

On Wed, 06-Sep-2000 at 15:03:50 +0100, Cameron Grant wrote:
> SNDCTL_DSP_GETFMTS returns the hardware formats supported.
> SNDCTL_DSP_SETFMT is allowed to set other formats using software
> translation, and does so.

Hmm, let's see if I understood this correctly:

In case SNDCTL_DSP_GETFMTS returns 8 (don't worry about stereo) which
is AFMT_U8 and I set 0x10 (AFMT_S16_LE) and get returned 0x10 I can
assume that:

1. Recording will be done properly (but possibly with a different format)
2. The output data will conform to the format set with SNDCTL_DSP_SETFMT
   which is AFMT_S16_LE in my example.

Is this correct?

Thanks for looking into this,

	-Andre
Comment 4 gandalf 2000-09-06 15:28:18 UTC
> On Wed, 06-Sep-2000 at 15:03:50 +0100, Cameron Grant wrote:
> > SNDCTL_DSP_GETFMTS returns the hardware formats supported.
> > SNDCTL_DSP_SETFMT is allowed to set other formats using software
> > translation, and does so.
>
> Hmm, let's see if I understood this correctly:
>
> In case SNDCTL_DSP_GETFMTS returns 8 (don't worry about stereo) which
> is AFMT_U8 and I set 0x10 (AFMT_S16_LE) and get returned 0x10 I can
> assume that:
>
> 1. Recording will be done properly (but possibly with a different format)
> 2. The output data will conform to the format set with SNDCTL_DSP_SETFMT
>    which is AFMT_S16_LE in my example.
>
> Is this correct?

yes.  in theory at least.  if you get garbage out, there's a bug in the
code.

    -cg
Comment 5 Andre Albsmeier 2000-09-06 15:37:28 UTC
On Wed, 06-Sep-2000 at 15:28:18 +0100, Cameron Grant wrote:
> > On Wed, 06-Sep-2000 at 15:03:50 +0100, Cameron Grant wrote:
> > > SNDCTL_DSP_GETFMTS returns the hardware formats supported.
> > > SNDCTL_DSP_SETFMT is allowed to set other formats using software
> > > translation, and does so.
> >
> > Hmm, let's see if I understood this correctly:
> >
> > In case SNDCTL_DSP_GETFMTS returns 8 (don't worry about stereo) which
> > is AFMT_U8 and I set 0x10 (AFMT_S16_LE) and get returned 0x10 I can
> > assume that:
> >
> > 1. Recording will be done properly (but possibly with a different format)
> > 2. The output data will conform to the format set with SNDCTL_DSP_SETFMT
> >    which is AFMT_S16_LE in my example.
> >
> > Is this correct?
> 
> yes.  in theory at least.  if you get garbage out, there's a bug in the
> code.

Thanks. I think the latter is correct :-). I will investigate this a bit
more...

BTW, is my AWE 64 really not possible to do native 16 Bit recording?
IIRC, with voxware I could do 16 Bit recording but I might be wrong...

Thanks,

	-Andre
Comment 6 Andre Albsmeier 2000-09-06 18:39:06 UTC
On Wed, 06-Sep-2000 at 16:37:28 +0200, Andre Albsmeier wrote:
> On Wed, 06-Sep-2000 at 15:28:18 +0100, Cameron Grant wrote:
> > > On Wed, 06-Sep-2000 at 15:03:50 +0100, Cameron Grant wrote:
> > > > SNDCTL_DSP_GETFMTS returns the hardware formats supported.
> > > > SNDCTL_DSP_SETFMT is allowed to set other formats using software
> > > > translation, and does so.
> > >
> > > Hmm, let's see if I understood this correctly:
> > >
> > > In case SNDCTL_DSP_GETFMTS returns 8 (don't worry about stereo) which
> > > is AFMT_U8 and I set 0x10 (AFMT_S16_LE) and get returned 0x10 I can
> > > assume that:
> > >
> > > 1. Recording will be done properly (but possibly with a different format)
> > > 2. The output data will conform to the format set with SNDCTL_DSP_SETFMT
> > >    which is AFMT_S16_LE in my example.
> > >
> > > Is this correct?
> > 
> > yes.  in theory at least.  if you get garbage out, there's a bug in the
> > code.
> 
> Thanks. I think the latter is correct :-). I will investigate this a bit
> more...
> 
> BTW, is my AWE 64 really not possible to do native 16 Bit recording?
> IIRC, with voxware I could do 16 Bit recording but I might be wrong...

I assume there is really a problem when recording a 16 Bit format 
with a soundcard that is only capable of doing AFMT_U8...

Randall, can I include your dsp-record.c in a mail for Cameron?
This would make explaining the problem much easier...

Thanks,

	-Andre
Comment 7 aa8vb 2000-09-06 22:43:21 UTC
Andre Albsmeier:
 |On Wed, 06-Sep-2000 at 16:37:28 +0200, Andre Albsmeier wrote:
 |> BTW, is my AWE 64 really not possible to do native 16 Bit recording?
 |> IIRC, with voxware I could do 16 Bit recording but I might be wrong...
 |
 |I assume there is really a problem when recording a 16 Bit format 
 |with a soundcard that is only capable of doing AFMT_U8...
 |
 |Randall, can I include your dsp-record.c in a mail for Cameron?
 |This would make explaining the problem much easier...

Sure.

And yes, with Voxware 16-bit stereo recording is no problem for cards with
a SB16-compatible DSP (SB16, SB32, AWE32, AWE64, etc.).  Mine is a SB32.

-- 
Randall Hopper
aa8vb@nc.rr.com
Comment 8 Andre Albsmeier 2000-09-07 05:54:17 UTC
On Wed, 06-Sep-2000 at 15:28:18 +0100, Cameron Grant wrote:
> > On Wed, 06-Sep-2000 at 15:03:50 +0100, Cameron Grant wrote:
> > > SNDCTL_DSP_GETFMTS returns the hardware formats supported.
> > > SNDCTL_DSP_SETFMT is allowed to set other formats using software
> > > translation, and does so.
> >
> > Hmm, let's see if I understood this correctly:
> >
> > In case SNDCTL_DSP_GETFMTS returns 8 (don't worry about stereo) which
> > is AFMT_U8 and I set 0x10 (AFMT_S16_LE) and get returned 0x10 I can
> > assume that:
> >
> > 1. Recording will be done properly (but possibly with a different format)
> > 2. The output data will conform to the format set with SNDCTL_DSP_SETFMT
> >    which is AFMT_S16_LE in my example.
> >
> > Is this correct?
> 
> yes.  in theory at least.  if you get garbage out, there's a bug in the
> code.

OK, here is more info: Randall has written a small program dsp-record.c
(I have just modified it a bit for -Wall):

------------------------- snip -----------------------

/*  Adapted from short pgm posted by dnelson@emsphone.com by  */
/*  Randall Hopper (rhh@ct.picker.com)                        */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/soundcard.h>
#include <sys/time.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define ARRAYSIZE(a) ( sizeof(a) / sizeof(a[0]) )

#define DO_IOCTL_SERR(str,arg)                               \
        do { fprintf(stderr, "ioctl(%s, %ld) failed: %s\n",     \
                          str, (long)arg, strerror(errno) ); \
          exit(1);                                           \
        } while (0)
#define DO_IOCTL_SVALERR(str,v1,v2)                          \
        do { fprintf(stderr, "ioctl(%s) failed: Tried %d, Got %d\n", \
                          str, v1, v2 );                     \
          exit(1);                                           \
        } while (0)


void StrLwr( char a[] )
{
    char *p = a;

    while ( *p != '\0' )
        *(p++) = tolower( *p );
}


/*  SampFmt_StrToEnum: Convert sample format string to enum; -1 on failure  */
int SampFmt_StrToEnum( char str[] )
{
    static struct {
        char *str;
        int   val;
    } Sfmtcnvt[] = {
        { "mulaw", AFMT_MU_LAW		},
        { "ulaw" , AFMT_MU_LAW		},
        { "alaw" , AFMT_A_LAW		},
        { "adpcm", AFMT_IMA_ADPCM	},
        { "8"    , AFMT_U8		},
        { "u8"   , AFMT_U8		},
        { "16"   , AFMT_S16_LE		},
        { "s16le", AFMT_S16_LE		},
        { "s16be", AFMT_S16_BE		},
        { "s8"   , AFMT_S8		},
        { "u16le", AFMT_U16_LE		},
        { "u16be", AFMT_U16_BE		},
        { "mpeg" , AFMT_MPEG		},
    };

    int  i;
    char tmpstr[20];

    tmpstr[0] = '\0';
    strncat( tmpstr, str, sizeof(tmpstr)-1 );
    StrLwr( tmpstr );

    for ( i = 0; i < ARRAYSIZE( Sfmtcnvt ); i++ )
        if ( strcmp( tmpstr, Sfmtcnvt[i].str ) == 0 )
            break;

    if ( i < ARRAYSIZE( Sfmtcnvt ) )
        return Sfmtcnvt[i].val;
    else
        return -1;
}


int main(int argc, char *argv[])
{
    int fd;
    int rate = 8012;
    int bits = 8;
    int rate_new, bits_new, chan_new;
    char bits_str[80] = "u8";
    int chan = 1;
    int duration = -1,
        elapsed;
    int c;
    int len;
    char buf[1024];
    struct timeval tv1,tv2;
    int  frag, frag_new;

    if ( (fd = open("/dev/dsp", O_RDONLY)) < 0 ) {
        fprintf( stderr, "Can't open /dev/dsp for read\n" );
        exit(1);
    }

    while ((c = getopt(argc, argv, "hr:b:c:d:")) != EOF)
    {
        switch (c)
        {
            case 'r':
                rate = atoi(optarg);
                break;
            case 'b':
                bits_str[0] = '\0';
                strncat( bits_str, optarg, sizeof(bits_str)-1 );
                bits = SampFmt_StrToEnum( bits_str );
                if ( bits < 0 ) {
                    fprintf( stderr, "Bad sample format\n" );
                    exit(1);
                }
                break;
            case 'c':
                chan = atoi(optarg);
                break;
            case 'd':
                duration = atoi(optarg);
                break;
            case '?':
            case 'h':
                fprintf( stderr, "dsp-record -r rate -b bits -c channels "
                                 "-d duration(sec)\n");
                exit(1);
        }
    }
    fprintf( stderr, "Trying    %d Hz %d Channel %s Format\n",
             rate, chan, bits_str );

    bits_new = bits;
    if ( ioctl(fd, SNDCTL_DSP_SETFMT, &bits_new) < 0 )
        DO_IOCTL_SERR( "SNDCTL_DSP_SETFMT", bits );
    if ( bits != bits_new )
        DO_IOCTL_SVALERR( "SNDCTL_DSP_SETFMT", bits, bits_new );

    rate_new = rate;
    if ( ioctl(fd, SOUND_PCM_WRITE_RATE, &rate_new) < 0 )
        DO_IOCTL_SERR( "SOUND_PCM_WRITE_RATE", rate );
    if ( rate != rate_new )
        DO_IOCTL_SVALERR( "SOUND_PCM_WRITE_RATE", rate, rate_new );

    chan_new = chan;
    if ( ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &chan_new) < 0 )
        DO_IOCTL_SERR( "SOUND_PCM_WRITE_CHANNELS", chan );
    ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &chan_new);
    if ( chan != chan_new )
        DO_IOCTL_SVALERR( "SOUND_PCM_WRITE_CHANNELS", chan, chan_new );

    fprintf( stderr, "Recording %d Hz %d Channel %s Format\n",
         rate, chan, bits_str );

    frag_new = frag = (127 << 16) | 10;
    if ( ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag_new) < 0 )
        DO_IOCTL_SERR( "SNDCTL_DSP_SETFRAGMENT", frag );
    if ( (frag_new & 0xffff) != 10 ) {
	fprintf( stderr, "SNDCTL_DSP_SETFRAGMENT:  Can't set 1k buffers\n" );
	exit(1);
    }

    gettimeofday( &tv1, NULL );
    tv1.tv_sec--, tv1.tv_usec += 1000000;

    while ((len = read(fd, buf, sizeof(buf))) > 0) {
        write(fileno(stdout), buf, len);
        if ( duration > 0 ) {
            gettimeofday( &tv2, NULL );
            elapsed = (tv2.tv_sec  - tv1.tv_sec ) * 1000 +
                      (tv2.tv_usec - tv1.tv_usec) / 1000;
            /*fprintf( stderr, "sec = %d\r", elapsed/1000 );*/
            if ( elapsed/1000 >= duration )
                break;
        }
    }
    close(fd);
    fprintf( stderr, "\n" );

    if ( len <= 0 ) {
        fprintf( stderr, "read() failed; errno = %s\n", strerror(errno) );
        exit(1);
    }
    exit(0);
}

------------------------- snip -----------------------


Now what we do is following:

We first record 3 seconds of sound in AFMT_U8. Then we transform
it into a WAV file to make it hearable. Finally, we examine
the result.

andre@bali:~/try>./dsp-record -r 44100 -b 8 -c 2 -d 3 > 8.raw
Trying    44100 Hz 2 Channel 8 Format
Recording 44100 Hz 2 Channel 8 Format

andre@bali:~/try>sox -t raw -r 44100 -u -b 8.raw -t wav -r 44100 -s -w 8.wav

andre@bali:~/try>pcmplay 8.wav

This works great for AFMT_U8 and AFMT_S8.  Now we do the same
using a 16 Bit format, AFMT_U16_LE:

andre@bali:~/try>./dsp-record -r 44100 -b u16le -c 2 -d 3 > 16.raw
Trying    44100 Hz 2 Channel u16le Format
Recording 44100 Hz 2 Channel u16le Format

andre@bali:~/try>sox -t raw -r 44100 -u -w 16.raw -t wav -r 44100 -s -w 16.wav

andre@bali:~/try>pcmplay 16.wav

This could ruin my speakers :-). If this is the correct way of testing
it, there is something wrong with the PCM driver or its format conversion.

Another thing Randall confirmed is that he also could do 16 Bit recordings with
his SB32 and voxware. Is this feature known to be missing in the PCM driver
because it is not implemented or should it work here as well?

Thanks,

	-Andre
Comment 9 Andre Albsmeier 2001-02-12 14:53:48 UTC
Please close this one, it is fixed...

	-Andre
Comment 10 dwmalone freebsd_committer freebsd_triage 2001-02-12 15:19:49 UTC
State Changed
From-To: open->closed

Closed at submitters request.