Re: h5400 h5500 h5550 OSS patches to support mono audio

From: Phil Blundell <pb_at_reciva.com>
Date: Sat, 26 Mar 2005 09:32:25 +0000

Jim, thanks for the patch.
 
I'm somewhat reluctant to apply this to the tree, though, unless there
is an overriding need to do so. Generally, we have taken the attitude
that the application software ought to be handling any necessary format
conversions, rather than implementing arbitrary transforms in the
kernel. Adding this stuff to the kernel drivers will mean that programs
work on some iPAQ models but not others, which would be a little
unfortunate.

Is there a strong reason why this stuff needs to be done in the kernel
for your applications?

p.

On Fri, 2005-03-25 at 17:56 -0500, Jim Kaba wrote:
> Hi,
> Attached are two patches to the 5400 OSS sound drivers to implement
> stereo to audio conversion. When queried, the drivers will claim support
> for mono input and output. When configured for mono mode, these changes
> will automatically convert the ak4535 "stereo" mic input to mono input as
> seen by the user-space code. These driver changes will convert a mono
> output stream from user-space to stereo for the ak4535 hardware.
>
> This code makes heavy reuse of code that was already in the drivers to
> handle the funky "8KHz-16KHz conversions" that were originally thought to
> be needed to compensate for hardware deficiencies, so thanks to the
> original author of that code for the leg up.
>
> These patches were pretty heavily tested with some custom audio apps and
> with the open-source audio conferencing tool RAT, so they should be good to
> go.
>
> These patches are made against today's (March 25) cvs HEAD. You may need
> to tweak the base kernel directory name at the top of each one to get it to
> apply smoothly.
>
> h5400_mono_conversion.patch1 contains all the changes required to the
> pxa-audio.c file to do the stereo<->mono conversion. It "lies" to the user
> application when it queries about the amount of data available for reading
> writing. This is done to compensate for that fact that the actual amount
> of data read or written will be off by a factor of 2 compared to what is
> physically available. (This is needed for optimized programs that want to
> do non-blocking reads).
>
> h5400_mono_conversion.patch2 contains some support changes to h5400-audio.c
> to let the user-space programs know that the driver supports mono data
> streams.
>
> Enjoy!
>
> Jim
>
> --
> ----------------------------------------------------------------------
> James T. Kaba
> Sarnoff Corporation There are 10 kinds of people in the world:
> jkaba_at_sarnoff.com those who understand binary, and those who don't.
> 609-734-2246
> plain text document attachment (h5400_mono_conversion.patch1)
> diff -Naur linux_head_march25/kernel/drivers/sound/pxa-audio.c linux_jtk/kernel/drivers/sound/pxa-audio.c --- linux_head_march25/kernel/drivers/sound/pxa-audio.c 2004-07-29 17:06:07.000000000 -0400 +++ linux_jtk/kernel/drivers/sound/pxa-audio.c 2005-03-25 17:48:56.000000000 -0500 @@ -328,8 +328,7 @@ audio_state_t *state = (audio_state_t *) file->private_data; audio_stream_t *s = state->output_stream; int chunksize, ret = 0; - static unsigned long copybuf[512]; - + static unsigned short copybuf[MAX_DMA_SIZE / sizeof(unsigned short)]; //May be overkill; we only need 1 frags worth if (ppos != &file->f_pos) return -ESPIPE; @@ -355,17 +354,21 @@ break; } - if (state->double_samples) + if (state->double_samples) // convert mono from user to stereo for hardware { int i; unsigned long *p; /* Feed the current buffer */ - chunksize = (s->fragsize - b->offset) / 4; - if (chunksize > sizeof(copybuf)) - chunksize = sizeof(copybuf); - if (chunksize > count) + chunksize = (s->fragsize - b->offset) / 2; // num bytes free in DMA frag buffer + // 1:2 sample doubling will fill the DMA buffer with 2x the amount of input data. + // Don't copy so much data that we overflow the DMA buff + + if (chunksize > sizeof(copybuf)) // limit to space available in our temp buffer + chunksize = sizeof(copybuf); + if (chunksize > count) // write only as much data as the user asked us to chunksize = count; + if (copy_from_user(copybuf, buffer, chunksize)) { up(&s->sem); @@ -374,20 +377,13 @@ p = (unsigned long *)(b->data + b->offset); - //FIXME: should only duplicate signal 4 times if the input - // is 8Khz Mono sound, if the input is also stereo - // then only two duplications are needed. Need to allow - // the mixer to remember if it has been set to mono/stereo - // mode (even though it is infact always stereo output) - for (i = 0; i < chunksize / 4; i++) - { - *p++ = copybuf[i]; - *p++ = copybuf[i]; - *p++ = copybuf[i]; - *p++ = copybuf[i]; + for (i = 0; i < chunksize / 2; i++) // copy the data 1 sample = 2 bytes at a time + { + // duplicate mono sample for stereo channels 1&2 + *p++ = (unsigned long) ( (copybuf[i] << 16) | copybuf[i]); } - b->offset += 4*chunksize; + b->offset += chunksize*2; // this is how much data we put into the DMA frag buffer buffer += chunksize; count -= chunksize; if (b->offset < s->fragsize) @@ -450,7 +446,7 @@ audio_state_t *state = file->private_data; audio_stream_t *s = state->input_stream; int chunksize, ret = 0; - static unsigned long copybuf[4096]; + static unsigned short copybuf[MAX_DMA_SIZE / sizeof(unsigned short)]; //May be overkill; we only need 1 frags worth if (ppos != &file->f_pos) return -ESPIPE; @@ -484,37 +480,40 @@ break; } - if(state->double_samples) + if(state->double_samples) //convert stereo from hardware to mono for user { int i; unsigned long *p; /* Grab data from current buffer */ - chunksize = (s->fragsize - b->offset) >> 2; - if (chunksize > (count)) + chunksize = (s->fragsize - b->offset)/2; // num bytes available in DMA frag buffer + // 2:1 decimation will run through the buffer twice as fast. + // We don't want to step outside the buffer, so limit chunksize to 1/2. + + if (chunksize > (count)) // limit to amount requested from userspace chunksize = count; - + if (chunksize > sizeof(copybuf)) // limit to space available in our temp buffer + chunksize = sizeof(copybuf); + p = (unsigned long *) (b->data + b->offset); - //FIXME again this is for converting 16khz stereo to 8khz mono - for (i = 0; i < chunksize / 4; i++) + // We want to convert from stereo from hardware to mono for user + for (i = 0; i < chunksize / 2; i++) //copying 1 sample = 2 bytes at a time { - unsigned int x,y; - x = *(const unsigned short *) p; p++; p++; - y = *(const unsigned short *) p; p++; p++; - copybuf[i] = x | (y << 16); + copybuf[i] = (*p) & 0xffff; p++; // copy only the valid part of the "stereo" sample } - if (copy_to_user(buffer, copybuf, chunksize)) + if (copy_to_user(buffer, copybuf, chunksize)) { up(&s->sem); return -EFAULT; } - b->offset -= chunksize << 2; - buffer += chunksize; + b->offset += chunksize*2; // this is how much of the DMA frag buffer we've read + buffer += chunksize; count -= chunksize; - if (b->offset > 0) + + if (b->offset < s->fragsize) { up(&s->sem); break; @@ -548,16 +547,20 @@ * allows for control operations without the need for * stopping the DMA channel if it is already running. */ + + // FIXME!?!? Shouldn't this part only be done once the whole frag is consumed e.g. if b->offset == s->fragsize b->offset = 0; b->dma_desc->ddadr &= ~DDADR_STOP; /* move the index to the next fragment */ if (++s->usr_frag >= s->nbfrags) s->usr_frag = 0; + } if ((buffer - buffer0)) ret = buffer - buffer0; + return ret; } @@ -797,6 +800,12 @@ inf.fragments = inf.bytes / s->fragsize; inf.fragsize = s->fragsize; inf.fragstotal = s->nbfrags; + // When doing stereo<->mono conversion, we tell the user there are only 1/2 the # bytes available + if (state->double_samples) + { + inf.bytes = inf.bytes/2; + inf.fragsize = inf.fragsize/2; + } return copy_to_user((void *) arg, &inf, sizeof (inf)); }
> plain text document attachment (h5400_mono_conversion.patch2)
> diff -Naur linux_head_march25/kernel/drivers/sound/h5400-audio.c linux_jtk/kernel/drivers/sound/h5400-audio.c --- linux_head_march25/kernel/drivers/sound/h5400-audio.c 2005-02-26 05:13:15.000000000 -0500 +++ linux_jtk/kernel/drivers/sound/h5400-audio.c 2005-03-25 18:18:32.000000000 -0500 @@ -39,7 +39,7 @@ #include "pxa-i2s.h" #undef DEBUG -#undef DEBUG_PROC +#define DEBUG_PROC #ifdef DEBUG #define DPRINTK( x... ) printk( ##x ) #else @@ -49,6 +49,7 @@ #define AUDIO_NAME "H5400" #define AUDIO_RATE_DEFAULT 44100 +#define AUDIO_CHANNELS_DEFAULT 2 /* Put this inside audio_state_t some day. */ static struct i2c_client *ak4535; @@ -174,6 +175,7 @@ */ static long audio_samplerate = AUDIO_RATE_DEFAULT; +static long audio_channels = AUDIO_CHANNELS_DEFAULT; static void h5400_set_samplerate(long val) { @@ -239,18 +241,36 @@ switch (cmd) { case SNDCTL_DSP_STEREO: ret = get_user(val, (int *) arg); - if (ret) - return ret; - /* the AK4535 is stereo only */ - ret = (val == 0) ? -EINVAL : 1; - audio_state.double_samples = 0; + if (val == 0) { + ret = 0; audio_channels = 1; + audio_state.double_samples = 1; + } + else if (val == 1) { + ret = 1; audio_channels = 2; + audio_state.double_samples = 0; + } + else + ret = -EINVAL; + return put_user(ret, (int *) arg); case SNDCTL_DSP_CHANNELS: + ret = get_user(val, (int *) arg); + if (val == 1) { + ret = audio_channels = 1; + audio_state.double_samples = 1; + } + else if (val == 2) { + ret = audio_channels = 2; + audio_state.double_samples = 0; + } + else + ret = -EINVAL; + + return put_user(ret, (int *) arg); + case SOUND_PCM_READ_CHANNELS: - /* the AK4535 is stereo only */ - audio_state.double_samples = 0; - return put_user(2, (long *) arg); + return put_user(audio_channels, (long *) arg); case SNDCTL_DSP_SPEED: ret = get_user(val, (long *) arg); @@ -286,6 +306,7 @@ hw_shutdown: h5400_audio_shutdown, client_ioctl: h5400_audio_ioctl, sem: __MUTEX_INITIALIZER(audio_state.sem), + double_samples: 0, }; static int h5400_audio_open(struct inode *inode, struct file *file)
> _______________________________________________ H5400-port mailing list H5400-port@handhelds.org https://www.handhelds.org/mailman/listinfo/h5400-port
Received on Sat Mar 26 2005 - 04:35:03 EST

This archive was generated by hypermail 2.2.0 : Mon Jul 25 2005 - 17:20:11 EDT