|
Be
Driven
|
Device Drivers in the Be Os |
|
|
|
|
|
Old
Sound Card Device Driver (ALPHA) |
|
Audio Support and BeOs 4.0
Welcome to the, in transition world of Be. ;-)
I will let the following speak for itself.
"
> Will the OSS api Replace the existing sound model, OR
> Are the OSS api calls mapped INTO the BeOS sound model?
Not at all. The OSS API will sit side by side with BeOS's Audio
Manager. There will be some integration between the two drivers at
some later date so that OSS looks just like a BeOS driver to the Audio
Manager. In Linux, all of the apps go directly to OSS so there will
be no audio manager support for such apps on BeOS. But BeOS apps that
want to use the OSS drivers will be able to go via audio manager to
OSS.
> If there is a move to the OSS api, how will the old drivers
be supported, or
> are they a throw away?
>
Who knows?. At this point in time, there is no commitment from BeOS
to use the OSS API natively so we're going to be just another "application"
that sits on BeOS except that we're a device driver as well and we
have our own API.
best regards
Dev Mazumdar
4Front Technologies <info@opensound.com>
"
So as you can see, things are a changing, but You can still use
your old drivers for a while yet. Why write an old driver?
Because It still works! And if you want to get into the market quick,
to get familiar with how to write Be Drivers, etc. And maybe even
more pragmatically, it may be easier to implement a simpler interface,
in the short term as a stepping stone and proof of theory.
Of course when the new Api rears its head, I will add a new chapter
;-) And you will want to move to it!
Writting Version 4.0 Sound Card Drivers
Lets start with what we can find in the headers..
<Drivers.h>
B_AUDIO_DRIVER_BASE = 8000, /* base for codes in audio_driver.h */
Okay, The issue is now, where is this audio_driver.h file? Well, it's
not around any more..
So, what we will do is start by finding a working driver.
Get onto the net and get the es1370_130.zip file from BeWare. It
is there, and it has all the source open to the public. This is a
very easy to read driver, and if you are going to make one, be sure
to have it to get some ideas.
Copyright (C) 1998 HockYiung Huang (leclec@pacific.net.sg)
Port to R4 (C) 1999 Marc Schefer (mschefer@iprolink.ch)
Released under the GNU General Public License
[And before you go bash me up, there will be a GNU GPL on the web
page for all to see]
We cut and paste a little out of their header, and you have a clean
audio_driver.h file at the end of this chapter.. ( Thanx Guys )
I have also heavily drawn from the 2 following articles.
Volume II, Issue 22; June 3, 1998
Writing A Sound Card Driver
By Marc Ferguson
Volume III, Issue 12, March 24, 1999
Updating An Old Sound Card Driver
By Marc Ferguson - marc@be.com
I have not drawn from the sonic_vibes driver source code because it
implements the Version 3.0 API. (which has a few minor differences).
It still may be interesting to look at if you are coming across problems
in the API that you don't understand.
Data Movement Model.
Before going into the commands, it will help if we just give you
a small insight into the way the audio-server wants to get stuff,
and how you are probably going to want to implement your driver.
The Audio Server feeds packets of data to you, for you to play with.
For each packet given to you to read or write into, you are to release
a semaphore to let them know things are ready to go..
If you look at the examples provided by the ES_1370 drivers, you
will find that the read and write code will HOLD the call until the
operation is finished.
To this end, they have created 2 lots of buffers. A Read and a Write
buffer, which is directly used by the Audio Hardware. The Data is
then pushed/poped from the packets being transffered to you by the
server.
This is probably the most obvious way to create a driver, but introduces
the overhead of extra memory copies.
At First, I thought that you could implement a driver as follows
:
" The SOUND_WRITE_BUFFER and READ_BUFFER
functions may return before completion of the task, you just have
to call the Release semaphore when the task is complete for the Audio-Server.
This could be led to imply that if you do your work clever, that
you only need to copy data directly from the buffers, as opposed to
working with your own extra buffers... This would simplify the driver
from implementation point of view ( locking issues ), and actually
be faster.. Now the assumption would be that the audio-server must
have the data allocated in a shared area arena, in kernel space so
that the hardware could automatically write into it.... I will put
out some feelers, but I bet your boots, thats what they have done.
So, how would you go about it?
Well, the buffering concept would require to threads from the audio
server.
Lets consider a Read scenario.
We would need 2 threads, One to Push, One to Pull for a Read operation.
The first thread Pushes packets into your driver.. It will be waiting
for you to return from the call to send you another packet...
Now, on the other side, we have a thread that is blocked on the
semaphore that you are given to release....
So, what you need to do is que the Packets that the audio-server
gives you. You take a packet, put it into the que, and return quickly..
Now the push thread goes, Cool, and gives you another buffer to write
into. Great you now have 2 buffers, of which 1 you are currently writing
into... Next thing you now, you will have 3-4 buffers waiting to be
filled whilst you copy directly into them, and push them out the door
by releasing them with the semaphore..
"
And this is probably how it was designed, given the PPC DMA operations
will work on ALL memory... But thanx to intel and the 16MB DMA problem,
the simpler model probably needs to be used.
It seems (from questions asked below) that the memory is not created
specially.
The IOCTL commands
These are the ioctl codes used by the audio server:
#include <Drivers.h>
enum {
SOUND_GET_PARAMS = B_DEVICE_OP_CODES_END,
SOUND_SET_PARAMS,
SOUND_SET_PLAYBACK_COMPLETION_SEM,
SOUND_SET_CAPTURE_COMPLETION_SEM,
SOUND_RESERVED_1, /* unused */
SOUND_RESERVED_2, /* unused */
SOUND_DEBUG_ON, /* unused */
SOUND_DEBUG_OFF, /* unused */
SOUND_WRITE_BUFFER,
SOUND_READ_BUFFER,
SOUND_LOCK_FOR_DMA
};
Configuration
The SOUND_GET_PARAMS and SOUND_SET_PARAMS
ioctls read and write a set of parameters which correspond to the
settings in the sound preferences panel. The ioctl argument
is a (sound_setup*) .
The Structure can be seen in the Header at the bottom of the file.
Semaphores
"The SOUND_SET_PLAYBACK_COMPLETION_SEM ioctl takes
a (sem_id*) argument which points to a semaphore that
must be released once for each buffer written. The semaphore should
be released when the data in the buffer is no longer needed.
The SOUND_SET_CAPTURE_COMPLETION_SEM ioctl takes a
(sem_id*) argument which points to a semaphore that must
be released once for each buffer read. The semaphore should be released
when the data in the buffer is valid. "
static status_t
control( void *cookie, uint32 op, void *data, size_t len )
{
dev_info *d = (dev_info *) cookie;
status_t err = EINVAL;
switch (op)
{
...
case SOUND_SET_PLAYBACK_COMPLETION_SEM:
d->playback.completion_sem = *(sem_id *)data;
return B_OK;
...
}
}
The SOUND_WRITE_BUFFER call is allowed to return before
the data in the buffer has been consumed but the playback completion
semaphore must be released when the buffer can be recycled.
The SOUND_READ_BUFFER ioctl takes an (audio_buffer_header*)
argument. The size and address of the data can be computed
as above.
The "time" slot should be written with the estimate of
the system_time() corresponding to the beginning of the
buffer. The call is allowed to return before the buffer is full but
the capture completion semaphore must be released as soon as the buffer
is full.
SOUND_WRITE_BUFFER,
SOUND_READ_BUFFER,
SOUND_LOCK_FOR_DMA
-----------------
Volume II, Issue 22; June 3, 1998
Writing A Sound Card Driver
By Marc Ferguson
A number of people have expressed interest in writing sound card
drivers for BeOS. This article describes the interface used by the
current audio server to communicate with sound card drivers. Note
that this interface is for the current audio server, and that the
current audio server will be replaced with something better in a future
release. But if you want to write a sound card driver and test it
with the current audio server, this is what you will have to support.
These are the ioctl codes used by the audio server:
#include <Drivers.h>
enum {
SOUND_GET_PARAMS = B_DEVICE_OP_CODES_END,
SOUND_SET_PARAMS,
SOUND_SET_PLAYBACK_COMPLETION_SEM,
SOUND_SET_CAPTURE_COMPLETION_SEM,
SOUND_RESERVED_1, /* unused */
SOUND_RESERVED_2, /* unused */
SOUND_DEBUG_ON, /* unused */
SOUND_DEBUG_OFF, /* unused */
SOUND_WRITE_BUFFER,
SOUND_READ_BUFFER,
SOUND_LOCK_FOR_DMA
};
The SOUND_SET_PLAYBACK_COMPLETION_SEM ioctl takes a (sem_id*) argument
which points to a semaphore that must be released once for each buffer
written. The semaphore should be released when the data in the buffer
is no longer needed.
The SOUND_SET_CAPTURE_COMPLETION_SEM ioctl takes a (sem_id*) argument
which points to a semaphore that must be released once for each buffer
read. The semaphore should be released when the data in the buffer
is valid.
The SOUND_WRITE_BUFFER ioctl takes an (audio_buffer_header*) argument
which is defined in MediaDefs.h:
typedef struct audio_buffer_header {
int32 buffer_number;
int32 subscriber_count;
bigtime_t time;
int32 reserved_1;
int32 reserved_2;
int32 reserved_3;
int32 reserved_4;
} audio_buffer_header;
The audio data immediately follows the audio_buffer_header in memory
and is in stereo signed 16-bit linear native-endian format. The size
in bytes of the audio data plus the audio_buffer_header is stored
in the "reserved_1" slot of the audio_buffer_header (the
audio server was written before the size argument to the ioctl call
was implemented). The size and address of the audio data can be derived
this way:
audio_buffer_header* header = (audio_buffer_header*) ioctl_arg;
int32 bytes_of_data = header->reserved_1 - sizeof(*header);
int16* addr_of_data = (int16*) (header + 1);
The driver can ignore the "buffer_number" and "subscriber_count"
slots of the buffer header and should store an estimate of the system_time()
corresponding to the beginning of the buffer in the "time"
slot of the buffer
header.
The SOUND_WRITE_BUFFER call is allowed to return before the data
in the buffer has been consumed but the playback completion semaphore
must be released when the buffer can be recycled.
The SOUND_READ_BUFFER ioctl takes an (audio_buffer_header*) argument.
The size and address of the data can be computed as above. The "time"
slot should be written with the estimate of the system_time() corresponding
to the beginning of the buffer. The call is allowed to return before
the buffer is full but the capture completion semaphore must be released
as soon as the buffer is full.
The SOUND_GET_PARAMS and SOUND_SET_PARAMS ioctls read and write
a set of parameters which correspond to the settings in the sound
preferences panel. The ioctl argument is a (sound_setup*) in the following
format:
enum adc_source {
line = 0, cd, mic, loopback
};
enum sample_rate {
kHz_8_0 = 0, kHz_5_51, kHz_16_0, kHz_11_025, kHz_27_42,
kHz_18_9, kHz_32_0, kHz_22_05, kHz_37_8 = 9,
kHz_44_1 = 11, kHz_48_0, kHz_33_075, kHz_9_6, kHz_6_62
};
enum sample_format {}; /* obsolete */
struct channel {
enum adc_source adc_source;
/* adc input source */
char adc_gain;
/* 0..15 adc gain, in 1.5 dB steps */
char mic_gain_enable;
/* non-zero enables 20 dB MIC input gain */
char cd_mix_gain;
/* 0..31 cd mix to output gain in -1.5dB steps */
char cd_mix_mute;
/* non-zero mutes cd mix */
char aux2_mix_gain;
/* unused */
char aux2_mix_mute;
/* unused */
char line_mix_gain;
/* 0..31 line mix to output gain in -1.5dB steps */
char line_mix_mute;
/* non-zero mutes line mix */
char dac_attn;
/* 0..61 dac attenuation, in -1.5 dB steps */
char dac_mute;
/* non-zero mutes dac output */
};
typedef struct sound_setup {
struct channel left;
/* left channel setup */
struct channel right;
/* right channel setup */
enum sample_rate sample_rate;
/* sample rate */
enum sample_format playback_format;
/* ignore (always 16bit-linear) */
enum sample_format capture_format;
/* ignore (always 16bit-linear) */
char dither_enable;
/* non-zero enables dither on 16 => 8 bit */
char mic_attn;
/* 0..64 mic input level */
char mic_enable;
/* non-zero enables mic input */
char output_boost;
/* ignore (always on) */
char highpass_enable;
/* ignore (always on) */
char mono_gain;
/* 0..64 mono speaker gain */
char mono_mute;
/* non-zero mutes speaker */
} sound_setup;
On PPC systems the "mic_attn" and "mic_enable"
parameters are used to control the amount of adc to dac "loopback"
instead of the microphone input level.
The SOUND_LOCK_FOR_DMA ioctl takes an (audio_buffer_header*) argument
but can be ignored except on Macintosh. A Macintosh driver should
call lock_memory() on the audio buffer with the B_DMA_IO flag.
The audio server opens the driver named "/dev/old/sound".
So when you have implemented these ioctl calls and you want to test
your driver with the audio server, you can either name it "/dev/old/sound"
or name it
something like "/dev/old/mydriver" and create a symbolic
link in your UserBootscript file:
ln -s /dev/old/mydriver /dev/old/sound
The "old" in this path name is to remind you that this
is an interface which will be deprecated in the future.
------------------------------------
Audio_Driver.h
#ifndef AUDIO_DRIVER_H
#define AUDIO_DRIVER_H
/* control codes implemented by the driver */
enum {
SOUND_GET_PARAMS = B_DEVICE_OP_CODES_END,
SOUND_SET_PARAMS,
SOUND_SET_PLAYBACK_COMPLETION_SEM,
SOUND_SET_CAPTURE_COMPLETION_SEM,
SOUND_RESERVED_1, /* unused */
SOUND_RESERVED_2, /* unused */
SOUND_DEBUG_ON, /* unused */
SOUND_DEBUG_OFF, /* unused */
SOUND_WRITE_BUFFER,
SOUND_READ_BUFFER,
SOUND_LOCK_FOR_DMA
};
enum { // adc source
line = 0, cd, mic, loopback
};
enum { // sample rate
kHz_8_0 = 0, kHz_5_51, kHz_16_0, kHz_11_025, kHz_27_42,
kHz_18_9, kHz_32_0, kHz_22_05, kHz_37_8 = 9,
kHz_44_1 = 11, kHz_48_0, kHz_33_075, kHz_9_6, kHz_6_62
};
uint32 sample_rate_table[] = {
8000, 5510, 16000, 11025, 27420,
18900, 32000, 22050, 0, 37800,
0, 44100, 48000, 33075, 9600, 6620
};
//enum sample_format {}; /* obsolete */
struct channel {
uint32 adc_source; /* adc input source */
char adc_gain; /* 0..15 adc gain, in 1.5 dB steps */
char mic_gain_enable; /* non-zero enables 20 dB MIC input gain */
char cd_mix_gain; /* 0..31 cd mix to output gain in -1.5dB steps */
char cd_mix_mute; /* non-zero mutes cd mix */
char aux2_mix_gain; /* unused */
char aux2_mix_mute; /* unused */
char line_mix_gain; /* 0..31 line mix to output gain in -1.5dB steps */
char line_mix_mute; /* non-zero mutes line mix */
char dac_attn; /* 0..61 dac attenuation, in -1.5 dB steps */
char dac_mute; /* non-zero mutes dac output */
char reserved_1;
char reserved_2;
};
typedef struct sound_setup {
struct channel left; /* left channel setup */
struct channel right; /* right channel setup */
uint32 sample_rate; /* sample rate */
uint32 playback_format; /* ignore (always 16bit-linear) */
uint32 capture_format; /* ignore (always 16bit-linear) */
char dither_enable; /* non-zero enables dither on 16 => 8 bit */
char mic_attn; /* 0..64 mic input level */
char mic_enable; /* non-zero enables mic input */
char output_boost; /* ignore (always on) */
char highpass_enable; /* ignore (always on) */
char mono_gain; /* 0..64 mono speaker gain */
char mono_mute; /* non-zero mutes speaker */
} sound_setup;
typedef struct {
int32 buffer_number;
int32 subscriber_count;
bigtime_t time;
int32 reserved_1;
int32 reserved_2;
bigtime_t sample_clock;
} audio_buffer_header;
enum {
STATE_RUN = 0,
STATE_PAUSE
};
/* the stream */
typedef struct {
sem_id completion_sem; /* completion semaphore */
uint32 state;
uint32 sample_rate;
physical_entry scatter [2];
int scat_index;
sem_id hw_lock;
area_id transfer_area;
void * data;
uint32 offset;
bigtime_t time;
bigtime_t sample_clock;
//int64 playback_total;
} stream;
/* the 'cookie' used to keep track of each logical device */
typedef struct {
pci_info pci; /* pci info for this device */
sound_setup * ss; /* -> sound setup */
int id; /* logical device id */
stream playback; /* playback stream */
int32 write_count; /* num of write buffer request pending*/
stream capture; /* capture stream */
int32 read_count; /* num of read buffer request pending */
} dev_info;
#endif
Questions
>Does the audio server give you buffers that exist in shared
memory in
>Kerenel space under the 16MB limit.
Nope, the buffers received in the ioctls or in (pcm_write for the
new API)
are in user space, not even necessarily locked or contiguous.
In other words, they are unusable for x86 ISA DMA.
For PCI DMA, they might be used in place; I'll leave that to someone
who knows more than me about PCI DMA.
Cheers,
--Jonathan
The Communal Be Documentation Site
1999 - bedriven.miffy.org |