- Remove files at shouldn't be in the tree.

This commit is contained in:
Nate Williams 1998-02-17 18:59:15 +00:00
parent a414d6056c
commit 17c85b4825
13 changed files with 0 additions and 4167 deletions

View File

@ -1,40 +0,0 @@
# Makefile for the sound documentation
ARTICLE=sound
INPUTS=
STYLES=
.SUFFIXES: .fig .ps
OTHERSTUFF=
FIGURES=
PSS=
ALLFIG= $(FIGURES)
COMPAT= psfigps.tex
ALLPS= $(ALLFIG:.fig=.ps)
ALLSRCS= $(ARTICLE).tex $(STYLES) $(ALLFIG) $(PSS) $(INPUTS) \
$(COMPAT) $(OTHERSTUFF) Makefile
.fig.ps:
fig2dev -L ps -m 0.5 $*.fig > $*.ps
all: $(ARTICLE).dvi pnp.dvi ioctl.dvi Makefile
$(ARTICLE).dvi: allfig $(ARTICLE).tex $(INPUTS) $(STYLES) $(PSS)
latex $(ARTICLE).tex
pnp.dvi: allfig pnp.tex $(INPUTS) $(STYLES) $(PSS)
latex pnp.tex
ioctl.dvi: allfig pnp.tex $(INPUTS) $(STYLES) $(PSS)
latex ioctl.tex
allfig: $(ALLPS)
clean:
rm -f $(ALLPS) a c *.dvi *.aux *.log rl
tgz: $(ALLSRCS)
tar cvf - $(ALLSRCS) | gzip -9 > $(ARTICLE).tgz

Binary file not shown.

View File

@ -1,917 +0,0 @@
\documentclass[11pt]{article}
\textwidth 6in
\textheight 9in
\oddsidemargin 0in
\evensidemargin 0in
\topmargin 0in
% \input{psfigps.tex}
\begin{document}
\author{Luigi Rizzo\\
Dipartimento di Ingegneria dell'Informazione -- Universit\`a di Pisa\\
via Diotisalvi 2 -- 56126 Pisa (Italy)\\
email: {\tt l.rizzo@iet.unipi.it}}
\title{The FreeBSD audio driver}
\maketitle
\begin{abstract}
In this paper we describe the architecture of the new FreeBSD audio
driver. We also give detailed information on the internal data
structures used by the driver, and examples on how to add support
for new devices.
{\bf WARNING:} since the code is rapidly evolving, there might be some
minor differences between this paper and the source code. In doubt,
the source code is the ultimate reference.
\end{abstract}
\section{Introduction}
The audio driver is the part of the kernel which handles audio-related
peripherals. In FreeBSD, historically, there has been not much support
for these devices. The original FreeBSD audio driver has been based on
the Voxware 3.0 release, originally developed for Linux and ported to
FreeBSD at 1.1 times. The driver in the source tree has been
essentially unsupported since then.
In parallel, some volunteers have been using a driver derived from
Voxware 3.5, which had support for full-duplex audio cards which have
become popular in more recent times.
Both versions of the Voxware code are extremely complex and not very
much in line with BSD device drivers. There are several motivations
for that, the main one (we believe) was that the code has evolved
through the years to support a number of boards which are sufficiently
similar to suggest not to write a completely different driver, and
sufficiently different to require the addition of state variables, new
support routines, and feature enahncements, to the existing driver.
The unfortunate result of this evolution was that the code in our hand
was extremely difficult to configure, follow, and in many cases unable to
support multiple devices. Furthermore, the complexity of the code,
prevented proper support for it.
The desire for a functional audio driver for FreeBSD, together with
the lack of support for the code currently in the source tree, and
its complexity of the original driver, were for us sufficient
motivations to start a large rewrite of the code.
From a user's point of view, we were especially concerned with the
complexity of the configuration process, which, to be done properly,
required a number of different devices to be compiled into the kernel,
with (at times) subtle dependencies among the modules and the options
to be used. So, one of our goal was to simplify the configuration
process, a goal which we believe to have achieved.
From the kernel developer's point of view, the Voxware driver was hard
to understand for two reasons. First, its structure was not much in
line with BSD driver. This obviously derives from the original
development on a different system. The second reason is the evolution
of the driver during time, which, we believe, has degraded the quality of
the code from a carefully designed original version.
We have started our work basing on Voxware 3.5. The Voxware code
served mainly as a reference on what the features of the various
boards were, and how problems had been dealt with. Our code
then evolved into a fairly complete rewrite of the main modules,
including the main configuration mechanisms, the DMA support
routines, and, in many cases, also the board-specific drivers which
could make use of many of the simplifications we have introduced.
\section{What the audio driver does}
The audio driver is intended to control all peripherals related to
audio handling. This includes digital audio sampling and generation
({\tt pcm}), communication with MIDI devices ({\tt midi}) and the
synthesizers ({\tt synth}) which several audio cards provide. In
addition to these devices, which have explicit entries in the kernel
configuration file, audio cards generally provide a {\em mixer}
device, which controls audio paths within the board.
Our devices currently has only support for {\tt pcm} and mixer
devices.
\subsection{The {\tt pcm} device}
The {\tt pcm} device is in charge of the audio sampling and
generation. Such device is generally implemented by a CODEC, a piece
of hardware containing one or two A/D and D/A converters. Depending on
the board, the minimum and maximum sampling rate, the number of bits
per sample, and the data format (e.g. the availability of compressed
audio formats) varies. At a minimum, all boards seem to be capable of
sampling mono audio with 8-bit unsigned values and 8 KHz resolutions.
Most boards (especially the Windows-Sound-System ones, known as WSS or
MSS) are also capable of full duplex stereo with 16-bit and 44.1KHz
sampling rate (corresponding to CD-quality).
The {\tt pcm} device implements the {\tt ioctl()} calls to select the
desired data format, and {\tt read()} and {\tt write()} functions to
allow a smooth exchange of data with the codec, and {\tt select()} to
notify the application when I/O is possible.
\subsection{The mixer}
The mixer controls audio data paths, i.e. which sources are directed
to the ADC input, which ones are directed to the audio output, and the
attenuation on the various channels. There is a wide variety of
features in this subsystem, as some boards do provide only on-off
control on a limited number of sources, whereas some boards provide
many sources and fine-grained volume controls.
We believe that a sufficiently general model of the {\tt mixer}
is one actually using {\em two} mixers, one to connect sources to the
ADC part of the codec, and one to connect sources to the physical
output (usually the speakers). Some early boards often implement the
mixer leading to the ADC only as a multiplexer with one active line,
but that can be modeled by thinking of a very coarse (1 bit) volume
control, and a restriction on the volumes on the various sources.
\section{Kernel Configuration}
The configuration of the driver is done through the following
statements included in the system configuration file:
\begin{verbatim}
device pcm0 at isa? port? tty irq N drq D flags F vector pcmintr
\end{verbatim}
The {\tt device pcm0} statement is in charge of bringing into the
kernel all modules related to audio support.
The main audio subsystem is configured by using the {\tt device
pcm0} statement. This line supplies the main parameters related to the
configuration of a single sound card, which are:
\begin{itemize}
\item {\bf port} the I/O address for the codec (and generally mixer)
subsystem. If no address is supplied, the driver looks at
different addresses for the most common boards.
\item {\bf irq} the interrupt line used by the board.
\item {\bf drq} the first DMA channel used by the board. Modern
sound boards supporting full-duplex can use two DMA channels,
so we use the {\tt flags} field to specify the second channel.
\item {\bf flags} used to specify the second DMA channel, the device
type (overriding the autoconfiguration mechanism), and
other information.
In particular, the flags have the following use:\\
\begin{center}
\begin{tabular}{|c|l|}
\hline Bits & Meaning \\
\hline \hline
2..0 & secondary (read) DMA channel for dual-dma boards. \\
\hline
4 & Enable secondary dma channel \\
\hline
15..8 & Specific device type/class. 0 means autoconfigure\\
\hline
\end{tabular}
\end{center}
\end{itemize}
A sound board often has more than the codec, and uses more than one
I/O address block. Since the {\tt device} line in the config file
does not permit to
specify more than one address (except, perhaps, by abusing the {\tt
flags} field, which we have already used though), we will
use two additional {\tt device} entries to specify the i/o address
for the MIDI ({\tt midi}) and Synthesizer ({\tt synth}) devices,
respectively. These entries do not correspond to actual devices
recognized by the kernel (in the sense that their probe routine will
always fail, and their attach routine will do nothing and will never
be called); rather, the parameters specified in these lines are used
to extend the description of the board provided by the {\tt device
pcmX} line.
There is no support at the moment for {\tt midi} and {\tt synth}
devices.
\subsection{Plug and Play support}
As a side effect of our work on the audio driver, we have implemented
support for Plug and Play (PnP) device configuration. This was a
necessity since many modern audio boards are configured through
PnP. PnP support is described in a separate document.
In order to support PnP boards, one device entry is still necessary
(i.e. {\tt pcm0}), to let the
configure program include all the necessary parameters. The entry must
include all fields, e.g.
\begin{verbatim}
device pcm0 at isa? port? tty irq 7 drq 1 vector pcmintr
\end{verbatim}
although
the actual configuration of PnP devices (i.e. port addresses, IRQ,
DMA, etc. will be auto detected, and unit numbers will be assigned
starting from the next free one (e.g. {\tt pcm1, pcm2, ...}).
\section{Code structure}
The following table details the various file which make up the code.
All files reside in the directory {\tt /sys/i386/isa/snd} .
\begin{center}
\begin{tabular}{|l|p{4in}|}
\hline
File Name & Description \\
\hline \hline
{\tt sound.c} & main device routines and autoconfiguration.\\
\hline
{\tt sound.h} & generic header for all kernel files.\\
\hline
{\tt soundcard.h} & generic header for all userland sound apps.\\
\hline
{\tt dmabuf.c} & DMA support. \\
\hline
{\tt sb\_dsp.c} & SoundBlaster driver \\
\hline
{\tt ad1848.c} & MSS-compatible driver \\
\hline
{\tt clones.c} & Miscellaneous code to enable SB/MSS emulation in
various boards. \\
\hline
\end{tabular}
\end{center}
\subsection{The {\tt snddev\_info} structure}
Each known board is fully described by a {\tt snddev\_info}
structure. The structure contains information of use at initialization
time (such as board type, name, probe and attach routines), and at
runtime. The latter include all board-specific functions, and the
generic status information about the board when in use.
{\tt snddev\_info} is made of two parts, the first one which is
generally initialized from a template which univoquely identifies the
board, the second one which contains run-time parameters. The first part
of the structure is:
\begin{verbatim}
typedef struct _snddev_info snddev_info ;
struct _snddev_info {
char name[64];
int type ; /* board type/class, search key during probes */
int (*probe)(struct isa_device * dev);
int (*attach)(struct isa_device * dev) ;
d_open_t *open ;
d_close_t *close ;
d_read_t *read ;
d_write_t *write ;
d_ioctl_t *ioctl ;
d_select_t *select ;
irq_proc_t *isr ;
snd_callback_t *callback;
int bufsize; /* DMA buffer size */
u_long audio_fmt ; /* bitmap of supported audio formats */
\end{verbatim}
where the names of the fields clearly identify the functions.
Board-specific code generally supplies, in the template, initializers
for all the above fields. At attach time, and before calling the
board-specific attach routines, the descriptor associated with the
device, {\tt pcm\_info[dev->id\_unit]} is bzeroed, initialized with
the template, then the fields in the second part of the structure
(shown next) are initialized with default values (partly taken from
the device config line, as in the case of i/o, irq and dma addresses),
and the required buffers are allocated.
\begin{verbatim}
int io_base, alt_base, conf_base,
mix_base, midi_base, synth_base;
int bd_id; /* board-specific id */
snd_dbuf dbuf_out, dbuf_in;
int status_len, status_ptr ; /* to implement sndstat */
volatile u_long flags; /* generic flags */
u_long bd_flags; /* board-specific flags */
int play_speed, rec_speed;
int play_blocksize, rec_blocksize;
u_long play_fmt, rec_fmt ;
u_long mix_devs, mix_rec_devs, mix_recsrc;
u_short mix_levels[32];
u_long interrupts;
void *device_data;
\end{verbatim}
The structure provides all generic fields for audio support, plus a
couple of board-specific fields, {\tt bd\_id} and {\tt bd\_flags}. The
former should be used to distinguish different versions of the same
device (e.g. major/minor id in case of the SoundBlaster, codec type
for MSS-compatible boards, etc.), the latter can be used to hold
board-specific flags. Should these info not be sufficient, room is
provided for a pointer to device-specific data so that the structure
can be extended at will.
Note that the descriptor has room for several base addresses. One of
this ({\tt io\_base}) is derived from the the kernel config line, and
corresponds to the base address for codec access. The others can be
derived using various mechanisms, e.g. hardwired info (some boards are
known to have a given subsystem at a given address), additional lines
in the config file, or PnP autoconfiguration info. These addresses are
used to access the subsystems, and to check for conflicts during the
configuration process.
Of particular importance is the {\tt flags} fields, which holds many
flags used to determine the status of the device. Among them:
\begin{verbatim}
#define SND_F_BUSY 0x0001 /* has been opened (for write) */
#define SND_F_BUSY_AUDIO 0x10000000
#define SND_F_BUSY_DSP 0x20000000
#define SND_F_BUSY_DSP16 0x40000000
\end{verbatim}
These flags indicate the busy status of the board. They are of
exclusive use of the device-specific {\tt open()} and {\tt close()}
routines. The intended purpose is to allow only one or more opens
on the device. For the way the kernel operates, when multiple
descriptors are open on the same device (e.g. because the process
{\tt fork()}'ed after opening the device), only the last {\tt
close()} will result in the invokation of the device-specific close.
But, since in the Voxware mode, a device is accessed using three
minor numbers (corresponding to {\tt /dev/dsp, /dev/audio, /dev/dspW}),
we have to keep track of opens issued on the different devices and
only call the close routines on the very last close.
\begin{verbatim}
#define SND_F_READING 0x0004 /* have a pending read */
#define SND_F_WRITING 0x0008 /* have a pending write */
\end{verbatim}
Allowing a single open does not suffice to exclude the possibility
that a process forks and then multiple calls are issued concurrently.
These two flags are used in the generic read/write routines to
prevent concurrent calls of the same type, and serialize calls of
different types for half-duplex devices. These two flags are
generally used in the generic read/write code, and there should be
no need to use them in the device-specific code, unless the read/write
routines are reimplemented.
\begin{verbatim}
#define SND_F_CLOSING 0x0040 /* a pending close */
#define SND_F_ABORTING 0x1000 /* a pending abort on write */
\end{verbatim}
These flags mark special conditions, which should be known to the
DMA routines to shut down the device properly. The {\tt CLOSING} flags
are generally set by a call to {\tt close()} so that DMA transfers are
shut down at the end of the current operation. The {\tt ABORTING}
flags are set in response to user breaks or special {\tt ioctl}'s
such as {\tt PAUSE}, and cause immediate suspension of the output.
\begin{verbatim}
#define SND_F_STEREO 0x0100
#define SND_F_XLAT8 0x0400 /* u-law <--> 8-bit unsigned */
#define SND_F_XLAT16 0x0800 /* u-law <--> 16-bit signed */
\end{verbatim}
These are generic flags, marking the use of stereo audio and the
need for special translations in order to support {\tt /dev/audio}
on boards which do not have native $\mu$LAW support. In practice,
the latter flags are only used for the SoundBlaster; furthermore,
conversion between $\mu$LAW and 16-bit signed is currently not
implemented (but required to implement full-duplex through {\tt
/dev/audio} on the SB16).
\begin{verbatim}
#define SND_F_INIT 0x4000 /* changed parameters. need init */
\end{verbatim}
Finally, this flag marks the need for board reinitialization at the
next convenient time. This is because some parameters (e.g. speed,
channels, etc.) might be changed while an I/O operation is in progress
(e.g. a previous write is being flushed), and the board can only be
reprogrammed at the end of a transfer. This flag helps in supporting
the functionality. In the generic {\tt ioctl} handler, as an example,
all calls which set a parameter update the descriptor, set the flag,
and then invoke the callback function to apply the change. If successful,
the flag will be cleared, otherwise it will stay set for the update to
take place at the next convenient time.
\subsection{Autoconfiguration}
The main component of the driver is the file {\tt sound.c} which
contains the autoconfiguration stuff and the main device routines.
The configuration process occurs as follows (see the following
diagram).
\begin{verbatim}
pcmprobe(dev) {
foreach (p in supported devices)
if (p->probe(dev) is successful) {
snddev_last_probed = p ;
return 1 ;
}
return 0 ; /* fail */
}
pcmattach(dev) {
initialize pcm_info[dev->id_unit] with data from the template,
from snddev_last_probed, and from dev;
call snddev_last_probed->attach(dev);
}
xxx_probe(dev) {
try to identify the board, possibly updating dev; can use
pcm_info[dev->id_unit] as temporary storage (reset on exit);
return 1 on success, 0 on failure.
}
xxx_attach(dev) {
initialize board and do the necessary allocation, complete the
info in pcm_info[dev->id_unit] (initialized from the template
and dev)
}
\end{verbatim}
The kernel invokes {\tt pcmprobe()} to detect a board, passing it
the parameters from the configuration line. The routine then scans
the set of supported devices to see if the device is compatible
with the configured unit, and in case invokes the probe routine,
which is passed a pointer to the {\tt isa\_device} structure
describing the device.
Compatibility is checked using the following rules. If a device
type is specified, only that device is checked for. If the ``device
type'' refers to a class of devices (e.g. all SoundBlaster-like
boards, all MSS-like boards), then all known devices in that class
are probed. Finally, if no device type is specified, then all
devices are probed. Internally, a {\tt snddev\_info} structure is
defined for each known board. For each board class, an array holds
pointers to the descriptors of each board. Finally, an array holds
pointers to the arrays describing each board class.
The probe routine is not supposed to do any resource allocation,
just to check for the presence of the board. The only allowed side
effect of the probe routine is to modify the content of the {\tt
isa\_device} structure, in case a generic parameter was passed, to
instantiate it to the correct value. If the board-specific probe
routine is successful, so is {\tt pcmprobe()}. This will result in
the invokation of {\tt pcmattach()}, with a pointer to the same
{\tt isa\_device} structure that was used at probe time.
{\tt pcmattach()} will now copy the {\tt snddev\_info} structure
describing the successfully-probed device into a permanent device
descriptor, fill it up with run-time information, and call the
board-specific attach routine. All the above is only possible
because {\tt pcmprobe()} saves a pointer ({\tt last\_probed}) to
the {\tt snddev\_info} corresponding to the successfully probed
device before returning.
The board-specific attach routines has the task of configuring the
boards using the supplied parameters. The only parameter passed
to the attach routine is the {\tt isa\_device} pointer. This
structure includes the unit number, so the board-specific can
identify the permanent device descriptor as {\tt pcm\_info[unit]}
(use {\tt midi\_info}, {\tt synth\_info} for other devices).
Although the return type is {\tt int}, the return value from the
board-specific attach routine is not checked, similarly to what is
done for the generic ISA attach routines. The only action that the
kernel does on a successful attach is to register an interrupt for
the device. As a consequence, a failed attach can be marked by
setting {\tt dev->id\_irq = 0 ;} which has the consequence of not
registering an interrupt for the device.
\subsection{Main device routines}
Upon a successful attach, {\tt pcmattach()} registers a {\tt cdevsw}
entry for the audio device. All audio devices share the same routines,
{\tt sndopen, sndclose, sndread, sndwrite, sndioctl, sndselect},
which perform some generic actions and then invoke the board-specific
routines, if available. The board-specific routines, as well as
the probe and attach routines, have the same interface of a BSD
device driver. This was a design choice meant to simplify development:
one can, in principle, develop a driver outside the audio driver,
and then move it under the audio driver with minimal effort.
Additionally, this approach simplifies life to a kernel developer
since there are no new interfaces to be learned.
\subsubsection{{\tt sndopen()} and {\tt sndclose()} }
The generic open and close routines do very little, they basically
check that the unit and device number correspond to a supported
device, and then invoke the (required) open/close routines for
the specific board. There is little more the generic routines can
do, since board vary widely and the open and close actions are very
board-specific.
\subsubsection{{\tt sndread()} and {\tt sndwrite()} }
On the contrary, the generic read and write actions are very similar
across all boards. In all cases, checks must be done for valid unit
and device numbers. Then, concurrent operations of the same type
must be denied. Finally, operations of different types on half-duplex
devices must be properly sequenced. After these checks have been
done, the generic read/write code requires to move data between the
user buffer and the DMA buffer, in blocks of appropriate size.
\subsection{{\tt sndioctl()}}
The audio driver uses a very large number of {\tt ioctl}'s. Many
of these simply require to read, or set, parameters in the device
descriptor, so they can be implemented by the generic routine for
all boards. Other ioctl calls, instead, require board-specific
actions. We have devised a mechanism to allow board-specific ioctl
to handle the desired set of ioctl calls, leaving the generic
routine the task to deal with other, unhandled, calls.
The generic routine, after checking device and unit numbers, passes
control to the board-specific routine, if existing. This can do its
own processing and return the error status. If the return value is
{\tt ENOSYS} (error number 78, ``Function not implemented''), then
the generic ioctl {\tt switch} statement is invoked which performs
the generic actions.
The generic routine tries to implement as much as possible of the
sound calls, leaving little processing to the user code. In
particular, it is assumed that all calls which just read the status
of the device can fetch it directly from the audio descriptor.
Updates to the parameters are done in the generic ioctl by setting
the parameter in the descriptor (when possible, by checking that
values are acceptable), setting the {\tt SND\_F\_INIT} flag and
calling the device-specific {\tt callback()} function, if available.
This in turn will try to perform the necessary action or, if not
possible, leave the flag set for later operation.
\subsection{{\tt sndselect()}}
A properly working select routine is fundamental for a audio device,
because often an application has to handle multiple data streams.
While it is true that an application can determine how long
a read or write call will take to complete, knowing buffer sizes and
sample rates, it is certainly more convenient and efficient to have a
working {\tt select()}. The generic select routine should cover all
needs for devices using DMA. The body of the routine is very
standard. The only noticeable thing is that, when the user specified a
preferred block size through one of the available {\tt ioctl()} calls,
then {\tt select()} will return only when at least one whole block
can be transferred. In other cases (default) one byte will suffice to
make {\tt select()} return (although the system will still choose a blocksize
for DMA transfers, corresponding to 0.25~s of data).
\subsection{{\tt /dev/sndstat}}
In the Voxware driver, a special, readonly, status device ({\tt
/dev/sndstat}) was defined (minor number 6) which returned information
on the audio system, including supported devices and configured
devices. In our code, {\tt /dev/sndstat} is supported directly in
the generic routines (in file {\tt sound.c}) and is defined for
every unit returning the same information. In particular, the
status device returns data from a statically allocated 4~KB buffer
({\tt status\_buf}) which is filled-up at the first open with the
function {\tt init\_status()}.
\section{DMA support}
DMA handling routines are an important module of the audio code.
All the DMA-related code is in file {\tt dmabuf.c} and is completely
new for this release.
At the time of this writing, DMA support does not use AUTOMODE.
Using DMA mode, the sound card requests the transfer of data by
issuing DMA requests to the ISA DMA controller, which in turn
satisfies the request. The ISA DMA controller can be programmed with a
count, so that a signal (TC) is generated after the required amount of data
has been transferred. In ``AUTO'' mode, the ISA DMA controller
reinitializes itself at the end of the transfer. Otherwise, requests
are not served anymore until the controller is reprogrammed.
In principle, the codec could just forward the TC signal to the
appropriate interrupt line when enabled for DMA transfers. However,
many codec usually have a DMA count register themselves, and generate
an interrupt after the programmed number of bytes is transferred,
in many cases without looking at the value of TC. This makes it
possible to program the ISA DMA controller to use a large buffer,
while letting the codec generate interrupts on smaller (and perhaps
variable size) blocks.
There are several problems to be dealt with by the DMA code in the
audio driver. First, and most important, we need to avoid overruns
or underruns in transferring data from/to the audio board. This is
more important for (old) boards which do not have a properly sized
on-board FIFO. The second problem is to minimize the latency in
all i/o functions, something which is especially important for
full-duplex applications.
In the Voxware code, the DMA routines used a buffer partitioned into
a number of fixed-size fragments. The programmer should use ioctls to
select the fragment size which was best suited to his needs,
generally in terms of latency.
In our code, we use a completely different approach. The DMA buffer
is a single memory block divided into two, variable-size, areas:
{\em READY} and {\em FREE}. Each area is identified by an
offset into the buffer and a length. The data structure describing a
DMA buffer is the following:
\begin{verbatim}
typedef struct _snd_dbuf {
char *buf; /* pointer to the data buffer */
int bufsize ; /* total buffer size */
volatile int rp, fp; /* pointers to the ready and free area */
volatile int dl; /* transfer size */
volatile int rl, fl; /* length of ready and free areas. */
int int_count; /* how many interrupts on this channel */
int chan; /* dma channel */
int sample_size ; /* 1, 2, 4 */
struct selinfo sel; /* support for select */
u_long total; /* total bytes processed */
u_long prev_total; /* copy of the above when GETxPTR called */
} snd_dbuf ;
\end{verbatim}
The READY area contains
data ready to be transferred to the codec or to the user, depending
on the direction of the transfer. The FREE area is empty and
available for use. Both READY and FREE can wrap through the end of
the buffer. When the dma engine is in use, it transfers {\tt dl} bytes
from the beginning of the READY area (play) or to the beginning of the
FREE area (record).
The status of a dma transfer can in many cases be detected from the
value of the length components of the structures. If {\tt dl == 0},
then the DMA engine is not active. If {\em fl == 0}, a user write will
be blocking or a new DMA read cannot be started.
If {\em rl == 0}, a user read will be blocking, or a new DMA write
cannot be started. A idle dma descriptor has {\tt dl = rl = 0, fl
= bufsize}.
\subsection{Handling block sizes}
The purpose of the subdivision of buffers in three areas is that we
can have one pending DMA operation using the DMA area, some space for
the next DMA operation (the READY area for a write, the FREE area for
a READ), and a pending read or write on the third area.
Obviously we should avoid each of these areas to become too large and
eat all the available buffer space, or too small and make operations
inefficient. We will analize the details in the following two
sections.
\subsection{DMA write}
In write operations, the boundary between READY and FREE is advanced
to make room for user data. When possible, a DMA operation is
started by advancing the boundary between DMA and READY by the
amount of data which must be transferred. At the end of the DMA
transfer, the DMA area is shrunk to zero length by extending the
FREE area. These actions occur in the user write routine {\tt
dsp\_write\_body()}, and in the write interrupt service routine
{\tt dsp\_wrintr()}.
By using a straightforward implementation, the time required for the
user write routine to make data available to the DMA engine would be
proportional to the block size used in the write operation. This is
mainly because of the use of the {\tt uiomove} function in the
routine, possibly followed by a format conversion for translating
between $\mu$-LAW and the native format supported by the codec.
In order to minimize this latency, we do the transfer in blocks of
increasing size, doubling it at each pass. This gives us very low
latency, while at the same time enables the use of large blocks when
needed, without requesting the user to specify a block size.
\subsection{DMA read}
In read operations, a DMA operation needs to be started first, by
advancing the boundary between DMA and FREE by the amount of required
data. When the DMA transfer is complete, the boundary between READY
and DMA is advanced. User reads can be served by removing data from
the READY area and advancing the boundary between FREE and READY
accordingly. These actions occur in the user read routine {\tt
dsp\_read\_body()} and in the read interrupt service routine {\tt
dsp\_rdintr()}.
Even for DMA read, we move data to user space in blocks of increasing
size, so as to minimize latency in freeing space in the buffer.
However, implementing the read poses another difficulty in deciding
when to return data to a requesting application. Typically,
a read request should return when the requested number of bytes is
available. If data are already available, we can simply copy them
and return. If no data is available and the DMA is inactive, we
can start it with the requested amount of data, and then wait for
the request to complete. However, we are in trouble if the read
request arrives when a DMA operation is already scheduled (a normal
situation) and the transfer length is (possibly much) larger than
the requested amount of data. In this case, the interrupt will only
arrive at the end of the operation, which might take a long time.
This is not a problem with writes, since data are already buffered
for the whole operation.
\subsection{Why this does not always work...}
Using single DMA mode avoids that, because of slow interrupt
response, samples are played beyond the end of the buffer, or
capture overwrites some old, still unread, data at the other end
of the buffer.
Single DMA mode has the problem of requiring fast interrupt
response to ensure a smooth data transfer. If the system is not quick
to reinitialize the DMA controller, some samples might be missed. To
mitigate the problem, some controllers provide an internal FIFO which
increases the allowable interrupt response time. As an example, a
codec with 16-samples FIFO at 8~KHz gives 2~ms to reinitialize the
codec itself, which is a reasonable time.
This of course assumes that
the codec makes good use of the FIFO e.g. in the play queue generates
an interrupt when the count goes to 0 even when the FIFO is full. Some
don't.
Another problem of using single DMA is that the same count is
programmed into the two devices. It appears that some codecs when
working in full duplex have the bad habit of forgetting to count some
cycles, resulting in interrupts not being generated.
So, to sum up, single DMA mode works very well with well-behaved
codecs and fast systems. When the system becomes slow, late interrupt
response might cause ``clicks'' in the output, or missing samples in
the input. When the codec is buggy, there might be deadlocks because
of missing interrupts (we have only seen these in full duplex on some
cards).
\subsection{Auto-mode DMA}
Auto-mode DMA refers to a special operating mode of the DMA engine
which does not require the CPU to reinitialize the channel at the
end of a transfer. Auto DMA can be enabled independently in the
ISA DMA controller and in the sound card. The advantage of auto
mode is that data can be transferred continuously and do not require
the interrupt service routine to have a low latency (if AUTO DMA
is used on both the ISA DMA controller and in the sound card).
Enabling AUTO DMA in the ISA DMA controller only saves the small time
needed to reinitialize the DMA controller, and can help preventing the
deadlock with broken codecs, since the ISA DMA controller will always
be available to serve DMA requests. A drawback of this approach is
that the codec might try to transfer data beyond the allowed space.
Enabling AUTO DMA in the codec only does not make any sense.
To overcome the problem of DMA transfers going beyond the allowed
space, the following strategy can be used. For playback, we must
insure that the region beyond the end of valid data contains data
which do not produce audible effects. As an example, it can be
initialized with replicas of the last sample (1, 2 or 4 bytes
depending on the operating mode). The amount of space to be
initialized in this way should correspond to the expected maximum
interrupt latency.
For record, the whole buffer should not be used to acquire data, so
that any overrun will just fill a guard region in the buffer, and not
overwrite old data. Again the size of the guard region should be
computed depending on the expected maximum interrupt latency.
\section{Board-specific routines}
All board-specific routines are contained in one or more board-specific
source files, and possibly some include files. The board-specific
file(s) should provide as a minimum a template {\tt snddev\_info}
describing the board, and the various functions referenced in the
template and not defined elsewhere. This generally includes the
probe, attach, open, close, the callback, and the interrupt
dispatcher.
In order to make board-specific files easily readable, we suggest
to use a standard structure for them. As a reference, one can
look at file {\tt sb\_dsp.c} which contains the driver for the
SoundBlaster board.
The standard structure begins with the copyright. Then the body of the
file is enclosed in the following lines:
\begin{verbatim}
#include <i386/isa/snd/sound.h>
#if NPCM > 0
... body of the file
#endif /* NPCM > 0 */
\end{verbatim}
where the {\tt "sound.h"} contains all the generic include files and
definitions for kernel audio modules. It also includes the
config-generated file {\tt "snd.h"} which defines the number of
statically-configured audio devices, NPCM (note that one is required
but often suffices, since PnP devices will use additional unit numbers
up to the maximum of {\tt NPCM\_MAX} which is currently defined as 8.
The total number of audio devices, which can be used and should be
incremented when PnP devices are detected, is held in the global
variable {\tt u\_long nsnd} which is initialized with NPCM.
Next come the {\tt \#include} for all board-specific include files,
followed by the prototype declaration for all static functions which go
into the {\tt snddev\_info}, and then prototypes for all static
functions in this module. We encourage to make functions static if not
used by other modules, to reduce pollution of the name space.
After function prototypes, put the (initialized) descriptors for the
board(s) supported by this file, e.g.
\begin{verbatim}
snddev_info sb_op_desc = {
"SoundBlaster", SNDCARD_SB,
sb_probe, sb_attach,
sb_open, sb_close, NULL /* rd */, NULL /* wr */,
sb_dsp_ioctl, sndselect, sbintr, sb_callback,
DSP_BUFFSIZE,
AFMT_U8,
}
\end{verbatim}
\subsection{{\tt xxx\_probe()}}
The board-specific probe routine is passed a pointer to a {\tt struct
isa\_device} which contains all information collected from the {\tt
device pcmX} line in the kernel configuration file. The probe code
should do the minimum action to detect the board type and see if
parameters are compatible with the board's features. It returns 1 in
case of success, 0 in case of failure.
The probe code is
not supposed to do any allocation. If some temporary storage is
necessary, the probe code can use {\tt pcm\_info[dev->id\_unit]},
which is available, although not initialized at this stage. This
is convenient since, often, the probe and attach routine share the
same code for determining the board type, and can store the detected
information right there. Note however that {\tt
pcm\_info[dev->id\_unit]} will be reinitialized at attach time, so that
data structure should not be regarded as persistent information, but
only as a convenient storage area.
\subsection{{\tt xxx\_attach()}}
Same as the probe routine, the attach routine is passed a pointer
to a {\tt struct isa\_device} with info from the configuration
file. This time, however, the generic attach routine will also
initialize {\tt pcm\_info[dev->id\_unit]} copying the template
corresponding to the device identified by the probe routine;
moreover, values from the kernel config line are also copied in
the correct places (in particular, this involves the various fields
which depend on the value of {\tt flags}.
\subsection{Interrupt dispatcher}
All audio devices register the same interrupt service routine ({\em
ISR}), {\tt pcmintr}. This merely calls the ISR which is specified
in the device descriptor, passing it the unit number for which the
interrupt has arrived (in practice, one is not aware of the presence
of {\tt pcmintr}, which could also disappear in the future, and
should write the ISR routine as a standard ISR for BSD systems.
The ISR mainly has to find out the interrupt reason (accessing {\tt
pcm\_info[unit]} to collect board specific parameters) and perform
whatever action is necessary. Usually, it is only necessary to call
{\tt dsp\_wrintr()} or {\tt dsp\_rdintr()}, and possibly clear the
interrupt flag in the codec.
\subsection{Callback routine}
The main interface between the generic audio code and the device
specific code is the callback routine which is specified in the device
descriptor. The callback routine is passed an {\tt int} argument which
describes the reason of the callback. The argument is made of a mode
(read and/or write) and a reason. Typically the callback is invoked
from the dma code, upon user writes or interrupt routines. The code in
the callback routine should typically perform codec-specific actions
to start/stop a dma transfer in the desired direction.
As an extension, the callback function can also be called by the
generic routines at open and close times. This only happens if
device-specific open and close are not implemented, and the
functionalities are implemented here.
Again this is done for convenience, since in many cases the open and
close routines share a common part (e.g. to check for device busy
conditions, etc.) which this way need not to be replicated in
different drivers.
\subsection{Mixer}
Sound cards generally have a mixer device, which is in charge of
controlling signal flow, attenuation and filtering on the various
paths. We mutuate the Voxware scheme in this driver, both because it
is reasonably flexible, and for compatibility with existing
applications. In this scheme, each of the possible mixer channels is
associated to a bit in a bitmask.
Setting levels for a mixer channel generally just requires to write a
value (associated to the level) in some location. Since the number of
bits used by various mixer devices largely differs from board to
board, the Voxware driver uses a description table to map channels
into the appropriate addresses. A table is made of an array of {\tt
mixer\_ent} entries, which are set with the {\tt PMIX\_ENT} and
{\tt MIX\_ENT} macros (all defined in {\tt sound.h}):
entries:
\begin{verbatim}
struct mixer_def {
u_int regno:7;
u_int polarity:1;
u_int bitoffs:4;
u_int nbits:4;
};
typedef struct mixer_def mixer_ent;
typedef struct mixer_def mixer_tab[32][2];
#define MIX_ENT(name, reg_l, pol_l, pos_l, len_l, reg_r, pol_r, pos_r, len_r) \
{{reg_l, pol_l, pos_l, len_l}, {reg_r, pol_r, pos_r, len_r}}
#define PMIX_ENT(name, reg_l, pos_l, len_l, reg_r, pos_r, len_r) \
{{reg_l, 0, pos_l, len_l}, {reg_r, 0, pos_r, len_r}}
void
change_bits(mixer_tab *t, u_char *regval, int dev, int chn, int newval);
\end{verbatim}
The table is used by {\tt change\_bits()}, which accepts a pointer
to a table, a pointer to the old value of the register, device
(0..31), channel (left or right), and the new value to be set, in
the range 0..100.
\end{document}

View File

@ -1,82 +0,0 @@
Various support files for the new sound driver for FreeBSD.
audio-voxware.cc
this is the driver for "vat" (tested with vat 4.0b2 in the
ports distribution) to make things work in full duplex. It
does _not_ use non-blocking i/o, otherwise vat will happily
eat most of your CPU time since select will often return true
even if less than one frame is ready.
The environment variable AUDIODEV will let you use different
sound cards if you have more than one. It can contain the unit
number (0, 1, 2, ...) or the full pathname.
As an additional bonus, you can select a fourth input which is
a ulaw file which is written to the network instead of the
input audio data.
CPU usage on my p5/133 in full duplex, local feedback and
silence suppression (most demanding configuration I believe):
PCM 3.5%
DVI 4.8%
LPC 11 %
GSM 23 %
auvoxware.c
this is the driver for "nas" (tested with nas 1.2b5). Works
fine in full duplex.
linux_a.c
driver for timidity, a midi-to-pcm converter. Using this program
(and if you have spare cpu cycles) you can play midi files to
/dev/audio without the need for a synthesis device.
linux.patch
this is a patch for 2.2.X users for the linux emulator. The
files to correct are in /sys/i386/linux, and this patch
implements a few ioctl which are used by the realvideo player
for linux (rvplayer). In order to use these patches you have
to recompile the linux_mod.o and install it in place as follows
cd /usr/src/lkm/linux
make
mv linux_mod.o /lkm/linux_mod.o
pcmio.c
a simple program to do i/o with the audio device. You can set
on the command line the device, the speed, data format, stereo
or mono... And if you use a bidirectional device, you can play
and record at the same time.
E.g.
pcmio -f 1 +rec,stereo,44100,s16 song
will record from /dev/audio1 stereo,16-bit data to the file "song"
pcmio +play,stereo,44100,s16,loop 50s:20s song
will continuosly play 50s from "song" (skipping the first 20s)
to /dev/audio
pcmio +rec,ulaw - | tee tapefile > /dev/audio
will record using ulaw (mono at 8KHz, default) to stdout, and
the pipe will both dump things to "tapefile" and to /dev/audio
to listen what you are recording (you could do the same with
"cat...").
soundbyte.c
the audio module for speak_freely
test.c
lets you monitor the status of the device (/dev/audio1) by
mmapping the descriptor and dumping to screen the interesting
fields.

View File

@ -1,435 +0,0 @@
/*
* Copyright (c) 1991-1993 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the Computer Systems
* Engineering Group at Lawrence Berkeley Laboratory.
* 4. Neither the name of the University nor of the Laboratory may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Full Duplex audio module for the new sound driver and full duplex
* cards. Luigi Rizzo, from original sources supplied by Amancio Hasty.
*
* This includes some enhancements:
* - the audio device to use can be in the AUDIODEV env. variable.
* It can be either a unit number or a full pathname;
* - use whatever format is available from the card (included split
* format e.g. for the sb16);
* - limit the maximum size of the playout queue to approx 4 frames;
* this is necessary if the write channel is slower than expected;
* the fix is based on two new ioctls, AIOGCAP and AIONWRITE,
* but the code should compile with the old driver as well.
*/
#include <osfcn.h>
#include <machine/soundcard.h>
#include "audio.h"
#include "mulaw.h"
#include "Tcl.h"
#define ULAW_ZERO 0x7f
extern const u_char lintomulawX[];
class VoxWare : public Audio {
public:
VoxWare();
virtual int FrameReady();
virtual u_char* Read();
virtual void Write(u_char *);
virtual void SetRGain(int);
virtual void SetPGain(int);
virtual void OutputPort(int);
virtual void InputPort(int);
virtual void Obtain();
virtual void Release();
virtual void RMute();
virtual void RUnmute();
virtual int HalfDuplex() const;
protected:
int ext_fd; /* source for external file */
u_char* readbuf;
u_short *s16_buf;
int play_fmt ;
#if defined(AIOGCAP) /* new sound driver */
int rec_fmt ; /* the sb16 has split format... */
snd_capabilities soundcaps;
#endif
};
static class VoxWareMatcher : public Matcher {
public:
VoxWareMatcher() : Matcher("audio") {}
TclObject* match(const char* fmt) {
if (strcmp(fmt, "voxware") == 0)
return (new VoxWare);
return (0);
}
} linux_audio_matcher;
VoxWare::VoxWare()
{
readbuf = new u_char[blksize];
s16_buf = new u_short[blksize];
memset(readbuf, ULAW_ZERO, blksize);
ext_fd = -1 ; /* no external audio */
iports = 4; /* number of input ports */
}
void
VoxWare::Obtain()
{
char *thedev;
char buf[64];
int d = -1;
if (HaveAudio())
abort();
thedev=getenv("AUDIODEV");
if (thedev==NULL)
thedev="/dev/audio";
else if ( thedev[0] >= '0' && thedev[0] <= '9' ) {
d = atoi(thedev);
sprintf(buf,"/dev/audio%d", d);
thedev = buf ;
}
fd = open(thedev, O_RDWR );
thedev=getenv("MIXERDEV");
if (thedev == NULL)
if (d < 0)
thedev = "/dev/mixer";
else {
sprintf(buf,"/dev/mixer%d", d);
thedev = buf ;
}
if (fd >= 0) {
int i = -1 ;
#if defined(AIOGCAP) /* new sound driver */
i = ioctl(fd, AIOGCAP, &soundcaps);
if (i == 0) {
snd_chan_param pa;
struct snd_size sz;
pa.play_rate = pa.rec_rate = 8000 ;
pa.play_format = pa.rec_format = AFMT_MU_LAW ;
switch (soundcaps.formats & (AFMT_FULLDUPLEX | AFMT_WEIRD)) {
case AFMT_FULLDUPLEX :
/*
* this entry for cards with decent full duplex. Use s16
* preferably (some are broken in ulaw) or ulaw or u8 otherwise.
*/
if (soundcaps.formats & AFMT_S16_LE)
pa.play_format = pa.rec_format = AFMT_S16_LE ;
else if (soundcaps.formats & AFMT_MU_LAW)
pa.play_format = pa.rec_format = AFMT_MU_LAW ;
else if (soundcaps.formats & AFMT_U8)
pa.play_format = pa.rec_format = AFMT_U8 ;
else {
printf("sorry, no supported formats\n");
close(fd);
fd = -1 ;
return;
}
break ;
case AFMT_FULLDUPLEX | AFMT_WEIRD :
/* this is the sb16... */
if (soundcaps.formats & AFMT_S16_LE) {
pa.play_format = AFMT_U8 ;
pa.rec_format = AFMT_S16_LE;
} else {
printf("sorry, no supported formats\n");
close(fd);
fd = -1 ;
return;
}
break ;
default :
printf("sorry don't know how to deal with this card\n");
close (fd);
fd = -1;
break;
}
ioctl(fd, AIOSFMT, &pa);
play_fmt = pa.play_format ;
rec_fmt = pa.rec_format ;
sz.play_size = (play_fmt == AFMT_S16_LE) ? 2*blksize : blksize;
sz.rec_size = (rec_fmt == AFMT_S16_LE) ? 2*blksize : blksize;
ioctl(fd, AIOSSIZE, &sz);
} else
#endif
{ /* setup code for old voxware driver */
}
Audio::Obtain();
}
}
/*
* note: HalfDuplex() uses a modified function of the new driver,
* which will return AFMT_FULLDUPLEX set in SNDCTL_DSP_GETFMTS
* for full-duplex devices. In the old driver this was 0 so
* the default is to use half-duplex for them. Note also that I have
* not tested half-duplex operation.
*/
int
VoxWare::HalfDuplex() const
{
int i;
ioctl(fd, SNDCTL_DSP_GETFMTS, &i);
#if 0
printf("SNDCTL_DSP_GETFMTS returns 0x%08x %s duplex\n",
i, i & AFMT_FULLDUPLEX ? "full":"half");
#endif
return (i & AFMT_FULLDUPLEX) ? 0 : 1 ;
}
void VoxWare::Release()
{
if (HaveAudio()) {
Audio::Release();
}
}
void VoxWare::Write(u_char *cp)
{
int i = blksize, l;
static int peak = 0;
if (play_fmt == AFMT_S16_LE) {
for (i=0; i< blksize; i++)
s16_buf[i] = mulawtolin[cp[i]] ;
cp = (u_char *)s16_buf;
i = 2 *blksize ;
}
else if (play_fmt == AFMT_S8) {
for (i=0; i< blksize; i++) {
int x = mulawtolin[cp[i]] ;
x = (x >> 8 ) & 0xff;
cp[i] = (u_char)x ;
}
i = blksize ;
} else if (play_fmt == AFMT_U8) {
/*
* when translating to 8-bit formats, need to implement AGC
* to avoid loss of resolution in the conversion.
* The peak is multiplied by 2^13
*/
for (i=0; i< blksize; i++) {
int x = mulawtolin[cp[i]] ;
#if 0 /* AGC -- still not complete... */
if (x < 0) x = -x ;
if (x > peak) peak = ( peak*16 + x - peak ) / 16 ;
else peak = ( peak*8192 + x - peak ) / 8192 ;
if (peak < 128) peak = 128 ;
/* at this point peak is in the range 128..32k
* samples can be scaled and clipped consequently.
*/
x = x * 32768/peak ;
if (x > 32767) x = 32767;
else if (x < -32768) x = -32768;
#endif
x = (x >> 8 ) & 0xff;
x = (x ^ 0x80) & 0xff ;
cp[i] = (u_char)x ;
}
i = blksize ;
}
#if 0 && defined(AIOGCAP)
int queued;
ioctl(fd, AIONWRITE, &queued);
queued = soundcaps.bufsize - queued ;
if (play_fmt == AFMT_S16_LE) {
if (queued > 8*blksize)
i -= 8 ;
} else {
if (queued > 4*blksize)
i -= 4 ;
}
#endif
for ( ; i > 0 ; i -= l) {
l = write(fd, cp, i);
cp += l;
}
}
u_char* VoxWare::Read()
{
u_char* cp;
int l=0, l0 = blksize, i = blksize;
static int smean = 0 ; /* smoothed mean to remove DC */
static int loops = 20 ;
cp = readbuf;
if (rec_fmt == AFMT_S16_LE) {
cp = (u_char *)s16_buf;
l0 = i = 2 *blksize ;
}
for ( ; i > 0 ; i -= l ) {
l = read(fd, cp, i);
cp += l ;
}
if (rec_fmt == AFMT_S16_LE) {
for (i=0; i< blksize; i++) {
#if 1 /* remove DC component... */
int mean = smean >> 13;
int dif = ((short) s16_buf[i]) - mean;
smean += dif ;
readbuf[i] = lintomulawX[ dif & 0x1ffff ] ;
#else
readbuf[i] = lintomulaw[ s16_buf[i] ] ;
#endif
}
}
else if (rec_fmt == AFMT_S8) {
for (i=0; i< blksize; i++)
readbuf[i] = lintomulaw[ readbuf[i]<<8 ] ;
}
else if (rec_fmt == AFMT_U8) {
for (i=0; i< blksize; i++)
readbuf[i] = lintomulaw[ (readbuf[i]<<8) ^ 0x8000 ] ;
}
if (iport == 3) {
l = read(ext_fd, readbuf, blksize);
if (l < blksize) {
lseek(ext_fd, (off_t) 0, 0);
read(ext_fd, readbuf+l, blksize - l);
}
}
return readbuf;
}
/*
* should check that I HaveAudio() before trying to set gain.
*
* In most mixer devices, there is only a master volume control on
* the capture channel, so the following code does not really work
* as expected. The only (partial) exception is the MIC line, where
* there is generally a 20dB boost which can be enabled or not
* depending on the type of device.
*/
void VoxWare::SetRGain(int level)
{
double x = level;
level = (int) (x/2.56);
int foo = (level<<8) | level;
if (!HaveAudio())
Obtain();
switch (iport) {
case 2:
case 1:
break;
case 0:
if (ioctl(fd, MIXER_WRITE(SOUND_MIXER_MIC), &foo) == -1)
printf("failed to set mic volume \n");
break;
}
if (ioctl(fd, MIXER_WRITE(SOUND_MIXER_IGAIN), &foo) == -1)
printf("failed set input line volume \n");
rgain = level;
}
void VoxWare::SetPGain(int level)
{
float x = level;
level = (int) (x/2.56);
int foo = (level<<8) | level;
if (ioctl(fd, MIXER_WRITE(SOUND_MIXER_PCM), &foo) == -1) {
printf("failed to output level %d \n", level);
}
pgain = level;
}
void VoxWare::OutputPort(int p)
{
oport = p;
}
void VoxWare::InputPort(int p)
{
int src = 0;
if (ext_fd >=0 && p != 3) {
close(ext_fd);
ext_fd = -1 ;
}
switch(p) {
case 3:
fprintf(stderr,"input from file %s\n", ext_fname);
if (ext_fd == -1)
ext_fd = open(ext_fname, 0);
if (ext_fd != -1)
lseek(ext_fd, (off_t) 0, 0);
break;
case 2:
src = 1 << SOUND_MIXER_LINE;
break;
case 1: /* cd ... */
src = 1 << SOUND_MIXER_CD;
break;
case 0 :
src = 1 << SOUND_MIXER_MIC;
break;
}
if ( ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &src) == -1 ) {
printf("failed to select input \n");
p = 0;
}
iport = p;
}
void VoxWare::RMute()
{
rmute |= 1;
}
void VoxWare::RUnmute()
{
rmute &=~ 1;
}
/*
* FrameReady must return 0 every so often, or the system will keep
* processing mike data and not other events.
*/
int VoxWare::FrameReady()
{
int i = 0;
int lim = blksize;
ioctl(fd, FIONREAD, &i );
if (rec_fmt == AFMT_S16_LE) lim = 2*blksize;
return (i >= lim) ? 1 : 0 ;
}
/*** end of file ***/

File diff suppressed because it is too large Load Diff

View File

@ -1,240 +0,0 @@
# This file tells config what files go into building a kernel,
# files marked standard are always included.
#
# $Id: files.i386,v 1.141.2.11 1997/04/04 04:35:46 gibbs Exp $
#
aic7xxx_asm optional ahc device-driver \
dependency "$S/dev/aic7xxx/*.[chyl]" \
compile-with "cd $S/dev/aic7xxx; make obj; make BINDIR=${.CURDIR} all install" \
no-obj no-implicit-rule \
clean "aic7xxx_asm"
#
aic7xxx_{seq,reg}.h optional ahc device-driver \
compile-with "./aic7xxx_asm ${INCLUDES} -o aic7xxx_seq.h -r aic7xxx_reg.h $S/dev/aic7xxx/aic7xxx.seq" \
no-obj no-implicit-rule before-depend \
clean "aic7xxx_seq.h aic7xxx_reg.h" \
dependency "$S/dev/aic7xxx/aic7xxx.{reg,seq} aic7xxx_asm"
#
linux_genassym optional compat_linux \
dependency "$S/i386/linux/linux_genassym.c $S/i386/linux/linux.h" \
compile-with "${CC} ${CFLAGS} ${PARAM} -UKERNEL -o $@ $<" \
no-obj no-implicit-rule \
clean "linux_genassym"
#
linux_assym.h optional compat_linux \
dependency "linux_genassym" \
compile-with "./linux_genassym > $@" \
no-obj no-implicit-rule before-depend \
clean "linux_assym.h"
#
i386/scsi/93cx6.c optional ahc device-driver
i386/apm/apm.c optional apm device-driver
i386/apm/apm_setup.s optional apm
i386/eisa/3c5x9.c optional ep device-driver
i386/eisa/aic7770.c optional ahc device-driver \
dependency "aic7xxx_reg.h $S/i386/eisa/aic7770.c"
i386/eisa/aha1742.c optional ahb device-driver
i386/eisa/bt74x.c optional bt device-driver
i386/eisa/eisaconf.c optional eisa
i386/eisa/if_vx_eisa.c optional vx device-driver
i386/eisa/if_fea.c optional fea device-driver
i386/i386/autoconf.c standard device-driver
i386/i386/cons.c standard
i386/i386/db_disasm.c optional ddb
i386/i386/db_interface.c optional ddb
i386/i386/db_trace.c optional ddb
i386/i386/i386-gdbstub.c optional ddb
i386/i386/exception.s standard
i386/i386/identcpu.c standard
i386/i386/in_cksum.c optional inet
# locore.s needs to be handled in Makefile to put it first. Otherwise it's
# now normal.
# i386/i386/locore.s standard
i386/i386/machdep.c standard
i386/i386/math_emulate.c optional math_emulate
i386/i386/mem.c standard
i386/i386/microtime.s standard
i386/i386/perfmon.c optional perfmon profiling-routine
i386/i386/perfmon.c optional perfmon
i386/i386/pmap.c standard
i386/i386/procfs_machdep.c standard
i386/i386/support.s standard
i386/i386/swtch.s standard
i386/i386/sys_machdep.c standard
i386/i386/trap.c standard
i386/i386/userconfig.c optional userconfig
i386/i386/vm_machdep.c standard
i386/ibcs2/ibcs2_fcntl.c optional ibcs2
i386/ibcs2/ibcs2_stat.c optional ibcs2
i386/ibcs2/ibcs2_ipc.c optional ibcs2
i386/ibcs2/ibcs2_msg.c optional ibcs2
i386/ibcs2/ibcs2_misc.c optional ibcs2
i386/ibcs2/ibcs2_other.c optional ibcs2
i386/ibcs2/ibcs2_signal.c optional ibcs2
i386/ibcs2/ibcs2_ioctl.c optional ibcs2
i386/ibcs2/ibcs2_socksys.c optional ibcs2
i386/ibcs2/ibcs2_sysi86.c optional ibcs2
i386/ibcs2/ibcs2_util.c optional ibcs2
i386/ibcs2/ibcs2_isc.c optional ibcs2
i386/ibcs2/ibcs2_isc_sysent.c optional ibcs2
i386/ibcs2/ibcs2_xenix.c optional ibcs2
i386/ibcs2/ibcs2_xenix_sysent.c optional ibcs2
i386/ibcs2/ibcs2_errno.c optional ibcs2
i386/ibcs2/ibcs2_sysent.c optional ibcs2
i386/ibcs2/ibcs2_sysvec.c optional ibcs2
i386/ibcs2/imgact_coff.c optional ibcs2
i386/isa/aha1542.c optional aha device-driver
i386/isa/aic6360.c optional aic device-driver
i386/isa/b004.c optional bqu device-driver
i386/isa/bt5xx-445.c optional bt device-driver
i386/isa/clock.c standard
i386/isa/cronyx.c optional cx device-driver
i386/isa/ctx.c optional ctx device-driver
i386/isa/cx.c optional cx device-driver
i386/isa/cy.c optional cy device-driver
i386/isa/diskslice_machdep.c standard
i386/isa/elink.c optional ep device-driver
i386/isa/elink.c optional ie device-driver
i386/isa/fd.c optional fd device-driver
i386/isa/ft.c optional ft device-driver
i386/isa/gpib.c optional gp device-driver
i386/isa/asc.c optional asc device-driver
i386/isa/gsc.c optional gsc device-driver
i386/isa/if_ar.c optional ar device-driver
i386/isa/if_cx.c optional cx device-driver
i386/isa/if_ed.c optional ed device-driver
i386/isa/if_eg.c optional eg device-driver
i386/isa/if_el.c optional el device-driver
i386/isa/if_ep.c optional ep device-driver
i386/isa/if_ex.c optional ex device-driver
i386/isa/if_fe.c optional fe device-driver
i386/isa/if_ie.c optional ie device-driver
i386/isa/if_ix.c optional ix device-driver
i386/isa/if_le.c optional le device-driver
i386/isa/if_lnc.c optional lnc device-driver
i386/isa/if_sr.c optional sr device-driver
i386/isa/if_ze.c optional ze device-driver
i386/isa/if_zp.c optional zp device-driver
i386/isa/isa.c optional isa device-driver
i386/isa/istallion.c optional stli device-driver
i386/isa/joy.c optional joy device-driver
i386/isa/kbdio.c optional psm device-driver
i386/isa/kbdio.c optional sc device-driver
i386/isa/kbdio.c optional vt device-driver
i386/isa/lpt.c optional lpt device-driver
i386/isa/labpc.c optional labpc device-driver
i386/isa/mcd.c optional mcd device-driver
i386/isa/mse.c optional mse device-driver
i386/isa/ncr5380.c optional nca device-driver
i386/isa/npx.c optional npx device-driver
i386/isa/pcaudio.c optional pca device-driver
i386/isa/matcd/matcd.c optional matcd device-driver
i386/isa/pcibus.c optional pci device-driver
i386/isa/pcicx.c optional ze device-driver
i386/isa/pcicx.c optional zp device-driver
i386/isa/pcvt/pcvt_drv.c optional vt device-driver
i386/isa/pcvt/pcvt_ext.c optional vt device-driver
i386/isa/pcvt/pcvt_kbd.c optional vt device-driver
i386/isa/pcvt/pcvt_out.c optional vt device-driver
i386/isa/pcvt/pcvt_sup.c optional vt device-driver
i386/isa/pcvt/pcvt_vtf.c optional vt device-driver
i386/isa/pnp.c optional pnp device-driver
i386/isa/prof_machdep.c optional profiling-routine
i386/isa/psm.c optional psm device-driver
i386/isa/qcam.c optional qcam device-driver
i386/isa/qcamio.c optional qcam device-driver
i386/isa/random_machdep.c standard
i386/isa/rc.c optional rc device-driver
i386/isa/scd.c optional scd device-driver
i386/isa/seagate.c optional sea device-driver
i386/isa/si.c optional si device-driver
i386/isa/si_code.c optional si device-driver
i386/isa/sio.c optional sio device-driver
i386/isa/snd/sound.c optional pcm device-driver
i386/isa/snd/dmabuf.c optional pcm device-driver
i386/isa/snd/ad1848.c optional pcm device-driver
i386/isa/snd/sb_dsp.c optional pcm device-driver
i386/isa/snd/clones.c optional pcm device-driver
i386/isa/spigot.c optional spigot device-driver
i386/isa/spkr.c optional speaker device-driver
i386/isa/stallion.c optional stl device-driver
i386/isa/syscons.c optional sc device-driver
i386/isa/tw.c optional tw device-driver
i386/isa/ultra14f.c optional uha device-driver
i386/isa/wd.c optional wdc device-driver
i386/isa/wd.c optional wd device-driver
i386/isa/atapi.c optional atapi device-driver
i386/isa/wcd.c optional wcd device-driver
i386/isa/wd7000.c optional wds device-driver
i386/isa/wt.c optional wt device-driver
i386/linux/imgact_linux.c optional compat_linux
i386/linux/linux_dummy.c optional compat_linux
i386/linux/linux_file.c optional compat_linux
i386/linux/linux_ioctl.c optional compat_linux
i386/linux/linux_ipc.c optional compat_linux
i386/linux/linux_locore.s optional compat_linux \
dependency "linux_assym.h"
i386/linux/linux_misc.c optional compat_linux
i386/linux/linux_signal.c optional compat_linux
i386/linux/linux_socket.c optional compat_linux
i386/linux/linux_stats.c optional compat_linux
i386/linux/linux_sysent.c optional compat_linux
i386/linux/linux_sysvec.c optional compat_linux
i386/linux/linux_util.c optional compat_linux
i386/scsi/aic7xxx.c optional ahc device-driver \
dependency "aic7xxx_{reg,seq}.h"
i386/scsi/bt.c optional bt device-driver
libkern/bcd.c standard
libkern/divdi3.c standard
libkern/inet_ntoa.c standard
libkern/index.c standard
libkern/mcount.c optional profiling-routine
libkern/moddi3.c standard
libkern/qdivrem.c standard
libkern/qsort.c standard
libkern/random.c standard
libkern/scanc.c standard
libkern/skpc.c standard
libkern/strcat.c standard
libkern/strcmp.c standard
libkern/strcpy.c standard
libkern/strlen.c standard
libkern/strncmp.c standard
libkern/strncpy.c standard
libkern/udivdi3.c standard
libkern/umoddi3.c standard
gnu/i386/fpemul/div_small.s optional gpl_math_emulate
gnu/i386/fpemul/errors.c optional gpl_math_emulate
gnu/i386/fpemul/fpu_arith.c optional gpl_math_emulate
gnu/i386/fpemul/fpu_aux.c optional gpl_math_emulate
gnu/i386/fpemul/fpu_entry.c optional gpl_math_emulate
gnu/i386/fpemul/fpu_etc.c optional gpl_math_emulate
gnu/i386/fpemul/fpu_trig.c optional gpl_math_emulate
gnu/i386/fpemul/get_address.c optional gpl_math_emulate
gnu/i386/fpemul/load_store.c optional gpl_math_emulate
gnu/i386/fpemul/poly_2xm1.c optional gpl_math_emulate
gnu/i386/fpemul/poly_atan.c optional gpl_math_emulate
gnu/i386/fpemul/poly_div.s optional gpl_math_emulate
gnu/i386/fpemul/poly_l2.c optional gpl_math_emulate
gnu/i386/fpemul/poly_mul64.s optional gpl_math_emulate
gnu/i386/fpemul/poly_sin.c optional gpl_math_emulate
gnu/i386/fpemul/poly_tan.c optional gpl_math_emulate
gnu/i386/fpemul/polynomial.s optional gpl_math_emulate
gnu/i386/fpemul/reg_add_sub.c optional gpl_math_emulate
gnu/i386/fpemul/reg_compare.c optional gpl_math_emulate
gnu/i386/fpemul/reg_constant.c optional gpl_math_emulate
gnu/i386/fpemul/reg_div.s optional gpl_math_emulate
gnu/i386/fpemul/reg_ld_str.c optional gpl_math_emulate
gnu/i386/fpemul/reg_mul.c optional gpl_math_emulate
gnu/i386/fpemul/reg_norm.s optional gpl_math_emulate
gnu/i386/fpemul/reg_round.s optional gpl_math_emulate
gnu/i386/fpemul/reg_u_add.s optional gpl_math_emulate
gnu/i386/fpemul/reg_u_div.s optional gpl_math_emulate
gnu/i386/fpemul/reg_u_mul.s optional gpl_math_emulate
gnu/i386/fpemul/reg_u_sub.s optional gpl_math_emulate
gnu/i386/fpemul/wm_shrx.s optional gpl_math_emulate
gnu/i386/fpemul/wm_sqrt.s optional gpl_math_emulate
gnu/i386/isa/dgb.c optional dgb device-driver
gnu/i386/isa/nic3008.c optional nic device-driver
gnu/i386/isa/nic3009.c optional nnic device-driver
pci/wd82371.c optional wd device-driver

View File

@ -1,41 +0,0 @@
--- linux.h.orig Tue Dec 3 16:47:28 1996
+++ linux.h Mon Nov 17 00:05:29 1997
@@ -491,6 +491,9 @@
#define LINUX_SNDCTL_DSP_GETOSPACE 0x500C
#define LINUX_SNDCTL_DSP_GETISPACE 0x500D
#define LINUX_SNDCTL_DSP_NONBLOCK 0x500E
+#define LINUX_SNDCTL_DSP_GETCAPS 0x500F
+#define LINUX_SNDCTL_DSP_GETIPTR 0x5011
+#define LINUX_SNDCTL_DSP_GETOPTR 0x5012
#define LINUX_SOUND_MIXER_WRITE_VOLUME 0x4d00
#define LINUX_SOUND_MIXER_WRITE_BASS 0x4d01
#define LINUX_SOUND_MIXER_WRITE_TREBLE 0x4d02
--- linux_ioctl.c.orig Sat Nov 9 22:10:15 1996
+++ linux_ioctl.c Mon Nov 17 10:20:14 1997
@@ -691,6 +691,26 @@
args->cmd = SNDCTL_DSP_NONBLOCK;
return ioctl(p, (struct ioctl_args *)args, retval);
+ case LINUX_SNDCTL_DSP_GETCAPS:
+ args->cmd = SNDCTL_DSP_GETCAPS;
+ return ioctl(p, (struct ioctl_args *)args, retval);
+
+ case LINUX_SNDCTL_DSP_GETIPTR:
+ args->cmd = SNDCTL_DSP_GETIPTR;
+ return ioctl(p, (struct ioctl_args *)args, retval);
+
+ case LINUX_SNDCTL_DSP_GETOPTR:
+ args->cmd = SNDCTL_DSP_GETOPTR;
+ { int a= ioctl(p, (struct ioctl_args *)args, retval);
+ struct count_info *p = (struct count_info *)(args->arg);
+#if 0
+ p->bytes += 128 ;
+ uprintf("GETOPTR bytes %d blk %d ptr %d\n",
+ p->bytes, p->blocks, p->ptr);
+#endif
+ return a;
+ }
+
case LINUX_SOUND_MIXER_WRITE_VOLUME:
args->cmd = SOUND_MIXER_WRITE_VOLUME;
return ioctl(p, (struct ioctl_args *)args, retval);

View File

@ -1,238 +0,0 @@
/*
* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka
* Toivonen <toivonen@clinet.fi>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 675
* Mass Ave, Cambridge, MA 02139, USA.
*
* linux_audio.c
*
* Functions to play sound on the VoxWare audio driver (Linux or FreeBSD)
*
*/
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#ifdef linux
#include <sys/ioctl.h> /* new with 1.2.0? Didn't need this under
* 1.1.64 */
#include <linux/soundcard.h>
#endif
#ifdef __FreeBSD__
#include <stdio.h>
#include <machine/soundcard.h>
#endif
#include "config.h"
#include "output.h"
#include "controls.h"
static int open_output(void); /* 0=success, 1=warning, -1=fatal
* error */
static void close_output(void);
static void output_data(int32 * buf, int32 count);
static void flush_output(void);
static void purge_output(void);
/* export the playback mode */
#define dpm linux_play_mode
PlayMode dpm = {
DEFAULT_RATE, PE_16BIT | PE_SIGNED,
-1,
{0}, /* default: get all the buffer fragments you
* can */
"Linux dsp device", 'd',
"/dev/dsp",
open_output,
close_output,
output_data,
flush_output,
purge_output
};
/*************************************************************************/
/*
* We currently only honor the PE_MONO bit, the sample rate, and the number
* of buffer fragments. We try 16-bit signed data first, and then 8-bit
* unsigned if it fails. If you have a sound device that can't handle either,
* let me know.
*/
static int
open_output(void)
{
int fd, tmp, i, warnings = 0;
/* Open the audio device */
fd = open(dpm.name, O_RDWR /* | O_NDELAY */);
if (fd < 0) {
ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: %s",
dpm.name, sys_errlist[errno]);
return -1;
}
/* They can't mean these */
dpm.encoding &= ~(PE_ULAW | PE_BYTESWAP);
/*
* Set sample width to whichever the user wants. If it fails, try the
* other one.
*/
i = tmp = (dpm.encoding & PE_16BIT) ? 16 : 8;
if (dpm.encoding & PE_16BIT) {
int fmt = AFMT_S16_LE ;
if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) < 0 || fmt != AFMT_S16_LE) {
fmt = AFMT_U8 ;
if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) < 0 || fmt != AFMT_U8) {
ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
"%s doesn't support 16- or 8-bit sample width",
dpm.name);
close(fd);
return -1;
}
ctl->cmsg(CMSG_WARNING, VERB_VERBOSE,
"Sample width adjusted to %d bits", tmp);
dpm.encoding ^= PE_16BIT;
warnings = 1;
}
}
if (dpm.encoding & PE_16BIT)
dpm.encoding |= PE_SIGNED;
else
dpm.encoding &= ~PE_SIGNED;
/*
* Try stereo or mono, whichever the user wants. If it fails, try the
* other.
*/
i = tmp = (dpm.encoding & PE_MONO) ? 0 : 1;
if ((ioctl(fd, SNDCTL_DSP_STEREO, &tmp) < 0) || tmp != i) {
i = tmp = (dpm.encoding & PE_MONO) ? 1 : 0;
if ((ioctl(fd, SNDCTL_DSP_STEREO, &tmp) < 0) || tmp != i) {
ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
"%s doesn't support mono or stereo samples",
dpm.name);
close(fd);
return -1;
}
if (tmp == 0)
dpm.encoding |= PE_MONO;
else
dpm.encoding &= ~PE_MONO;
ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, "Sound adjusted to %sphonic",
(tmp == 0) ? "mono" : "stereo");
warnings = 1;
}
/* Set the sample rate */
tmp = dpm.rate;
if (ioctl(fd, SNDCTL_DSP_SPEED, &tmp) < 0) {
ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
"%s doesn't support a %d Hz sample rate",
dpm.name, dpm.rate);
close(fd);
return -1;
}
if (tmp != dpm.rate) {
dpm.rate = tmp;
ctl->cmsg(CMSG_WARNING, VERB_VERBOSE,
"Output rate adjusted to %d Hz", dpm.rate);
warnings = 1;
}
/* Older VoxWare drivers don't have buffer fragment capabilities */
#ifdef SNDCTL_DSP_SETFRAGMENT
/* Set buffer fragments (in extra_param[0]) */
tmp = 2+ AUDIO_BUFFER_BITS ;
if (!(dpm.encoding & PE_MONO))
tmp++;
if (dpm.encoding & PE_16BIT)
tmp++;
tmp |= (dpm.extra_param[0] << 16);
i = tmp;
if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) {
ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
"%s doesn't support %d-byte buffer fragments", dpm.name, (1 << i));
/*
* It should still work in some fashion. We should use a secondary
* buffer anyway -- 64k isn't enough.
*/
warnings = 1;
}
#else
if (dpm.extra_param[0]) {
ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
"%s doesn't support buffer fragments", dpm.name);
warnings = 1;
}
#endif
dpm.fd = fd;
return warnings;
}
static void
output_data(int32 * buf, int32 count)
{
char *p;
int res, l;
if (!(dpm.encoding & PE_MONO))
count *= 2; /* Stereo samples */
if (dpm.encoding & PE_16BIT) {
/* Convert data to signed 16-bit PCM */
s32tos16(buf, count);
res = count*2;
} else {
/* Convert to 8-bit unsigned and write out. */
s32tou8(buf, count);
res = count ;
}
for (p = buf ; res > 0 ; res -= l ) {
l = write(dpm.fd, p, res);
if (l < 0) return ;
p += l ;
}
}
static void
close_output(void)
{
close(dpm.fd);
}
static void
flush_output(void)
{
ioctl(dpm.fd, SNDCTL_DSP_SYNC);
}
static void
purge_output(void)
{
ioctl(dpm.fd, SNDCTL_DSP_RESET);
}

View File

@ -1,278 +0,0 @@
/*
* This is a simple program which demonstrates use of mmapped DMA buffer
* of the sound driver directly from application program.
*
* This sample program works (currently) only with Linux, FreeBSD and BSD/OS
* (FreeBSD and BSD/OS require OSS version 3.8-beta16 or later.
*
* Note! Don't use mmapped DMA buffers (direct audio) unless you have
* very good reasons to do it. Programs using this feature will not
* work with all soundcards. GUS (GF1) is one of them (GUS MAX works).
*
* This program requires version 3.5-beta7 or later of OSS
* (3.8-beta16 or later in FreeBSD and BSD/OS).
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/soundcard.h>
#include <sys/time.h>
main()
{
int fd, sz, fsz, i, tmp, n, l, have_data=0, nfrag;
int caps;
int sd, sl=0, sp;
unsigned char data[500000], *dp = data;
struct buffmem_desc imemd, omemd;
caddr_t buf;
struct timeval tim;
unsigned char *op;
struct audio_buf_info info;
int frag = 0xffff000c; /* Max # fragments of 2^13=8k bytes */
fd_set writeset;
close(0);
if ((fd=open("/dev/dsp", O_RDWR, 0))==-1)
{
perror("/dev/dsp");
exit(-1);
}
/*
* Then setup sampling parameters. Just sampling rate in this case.
*/
tmp = 48000;
ioctl(fd, SNDCTL_DSP_SPEED, &tmp);
printf("Speed set to %d\n", tmp);
/*
* Load some test data.
*/
sl = sp = 0;
if ((sd=open("smpl", O_RDONLY, 0))!=-1)
{
sl = read(sd, data, sizeof(data));
printf("%d bytes read from file.\n", sl);
close(sd);
}
else perror("smpl");
if (ioctl(fd, SNDCTL_DSP_GETCAPS, &caps)==-1)
{
perror("/dev/dsp");
fprintf(stderr, "Sorry but your sound driver is too old\n");
exit(-1);
}
/*
* Check that the device has capability to do this. Currently just
* CS4231 based cards will work.
*
* The application should also check for DSP_CAP_MMAP bit but this
* version of driver doesn't have it yet.
*/
/* ioctl(fd, SNDCTL_DSP_SETSYNCRO, 0); */
/*
* You need version 3.5-beta7 or later of the sound driver before next
* two lines compile. There is no point to modify this program to
* compile with older driver versions since they don't have working
* mmap() support.
*/
if (!(caps & DSP_CAP_TRIGGER) ||
!(caps & DSP_CAP_MMAP))
{
fprintf(stderr, "Sorry but your soundcard can't do this\n");
exit(-1);
}
/*
* Select the fragment size. This is propably important only when
* the program uses select(). Fragment size defines how often
* select call returns.
*/
ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag);
/*
* Compute total size of the buffer. It's important to use this value
* in mmap() call.
*/
if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info)==-1)
{
perror("GETOSPACE");
exit(-1);
}
sz = info.fragstotal * info.fragsize;
fsz = info.fragsize;
/*
* Call mmap().
*
* IMPORTANT NOTE!!!!!!!!!!!
*
* Full duplex audio devices have separate input and output buffers.
* It is not possible to map both of them at the same mmap() call. The buffer
* is selected based on the prot argument in the following way:
*
* - PROT_READ (alone) selects the input buffer.
* - PROT_WRITE (alone) selects the output buffer.
* - PROT_WRITE|PROT_READ together select the output buffer. This combination
* is required in BSD to make the buffer accessible. With just PROT_WRITE
* every attempt to access the returned buffer will result in segmentation/bus
* error. PROT_READ|PROT_WRITE is also permitted in Linux with OSS version
* 3.8-beta16 and later (earlier versions don't accept it).
*
* Non duplex devices have just one buffer. When an application wants to do both
* input and output it's recommended that the device is closed and re-opened when
* switching between modes. PROT_READ|PROT_WRITE can be used to open the buffer
* for both input and output (with OSS 3.8-beta16 and later) but the result may be
* unpredictable.
*/
if ((buf=mmap(NULL, sz, PROT_WRITE, MAP_FILE|MAP_SHARED, fd, 0))==(caddr_t)-1)
{
perror("mmap (write)");
exit(-1);
}
printf("mmap (out) returned %08x\n", buf);
op=buf;
/*
* op contains now a pointer to the DMA buffer
*/
/*
* Then it's time to start the engine. The driver doesn't allow read() and/or
* write() when the buffer is mapped. So the only way to start operation is
* to togle device's enable bits. First set them off. Setting them on enables
* recording and/or playback.
*/
tmp = 0;
ioctl(fd, SNDCTL_DSP_SETTRIGGER, &tmp);
printf("Trigger set to %08x\n", tmp);
/*
* It might be usefull to write some data to the buffer before starting.
*/
tmp = PCM_ENABLE_OUTPUT;
ioctl(fd, SNDCTL_DSP_SETTRIGGER, &tmp);
printf("Trigger set to %08x\n", tmp);
/*
* The machine is up and running now. Use SNDCTL_DSP_GETOPTR to get the
* buffer status.
*
* NOTE! The driver empties each buffer fragmen after they have been
* played. This prevents looping sound if there are some performance problems
* in the application side. For similar reasons it recommended that the
* application uses some amout of play ahead. It can rewrite the unplayed
* data later if necessary.
*/
nfrag = 0;
while (1)
{
struct count_info count;
int p, l, extra;
FD_ZERO(&writeset);
FD_SET(fd, &writeset);
tim.tv_sec = 10;
tim.tv_usec= 0;
select(fd+1, &writeset, &writeset, NULL, NULL);
/*
* SNDCTL_DSP_GETOPTR (and GETIPTR as well) return three items. The
* bytes field returns number of bytes played since start. It can be used
* as a real time clock.
*
* The blocks field returns number of fragment transitions (interrupts) since
* previous GETOPTR call. It can be used as a method to detect underrun
* situations.
*
* The ptr field is the DMA pointer inside the buffer area (in bytes from
* the beginning of total buffer area).
*/
if (ioctl(fd, SNDCTL_DSP_GETOPTR, &count)==-1)
{
perror("GETOPTR");
exit(-1);
}
nfrag += count.blocks;
#ifdef VERBOSE
printf("\rTotal: %09d, Fragment: %03d, Ptr: %06d",
count.bytes, nfrag, count.ptr);
fflush(stdout);
#endif
/*
* Caution! This version doesn't check for bounds of the DMA
* memory area. It's possible that the returned pointer value is not aligned
* to fragment boundaries. It may be several samples behind the boundary
* in case there was extra delay between the actual hardware interrupt and
* the time when DSP_GETOPTR was called.
*
* Don't just call memcpy() with length set to 'fragment_size' without
* first checking that the transfer really fits to the buffer area.
* A mistake of just one byte causes seg fault. It may be easiest just
* to align the returned pointer value to fragment boundary before using it.
*
* It would be very good idea to write few extra samples to next fragment
* too. Otherwise several (uninitialized) samples from next fragment
* will get played before your program gets chance to initialize them.
* Take in count the fact thaat there are other processes batling about
* the same CPU. This effect is likely to be very annoying if fragment
* size is decreased too much.
*/
/*
* Just a minor clarification to the above. The following line alings
* the pointer to fragment boundaries. Note! Don't trust that fragment
* size is always a power of 2. It may not be so in future.
*/
count.ptr = (count.ptr/fsz)*fsz;
#ifdef VERBOSE
printf(" memcpy(%6d, %4d)", (dp-data), fsz);
fflush(stdout);
#endif
/*
* Set few bytes in the beginning of next fragment too.
*/
if ((count.ptr+fsz+16) < sz) /* Last fragment? */
extra = 16;
else
extra = 0;
memcpy(op+count.ptr, dp, fsz+extra);
dp += fsz;
if (dp > (data+sl-fsz))
dp = data;
}
exit(0);
}

View File

@ -1,236 +0,0 @@
/*
* pcmio.c -- a simple utility for controlling audio I/O
* (rate, channels, resolution...)
*
* (C) Luigi Rizzo 1998
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <machine/soundcard.h>
char * usage_string =
"Usage: %s [-f device] [+parameters] [len:skip] file { file }\n"
"where device is the device to be used (default /dev/audio)\n"
"[len:skip] is the subsection of the file to be played\n"
"with a '-' indicating the default\n"
"and parameters is a comma-separated list containing one or more of\n"
" N the sampling speed\n"
" stereo|mono \n"
" cd|u8|alaw|ulaw|s16 data format\n"
" loop loop (play cyclically)\n"
;
#define BLKSZ 32768
int format=AFMT_MU_LAW;
int rate=8000 ;
int stereo = 0 ;
char dev[128];
int audiodev;
int play = 1 ; /* default */
int loop = 0 ;
int skip = 0;
int size = -1 ;
extern char *optarg;
extern int optind, optopt, opterr, optreset;
int
usage(char *s)
{
printf(usage_string, s);
exit(0);
}
int
parse_len(char *s)
{
int par = -1;
int mul = 1;
int p = strlen(s);
if (p == 0)
return -1 ;
if (*s != '-')
par = atoi(s);
else
return -1 ;
switch(s[p-1]) {
case 'k':
case 'K':
mul = 1024;
break;
case 'm':
case 'M':
mul = 1024*1024;
break;
case 's':
mul = rate * (stereo+1);
if (format == AFMT_S16_LE)
mul += mul;
break;
}
return par*mul;
}
void
parse_fmt(char *fmt)
{
char *s;
int v, last = 0 ;
again:
while (*fmt && (*fmt == ' ' || *fmt == '\t')) fmt++;
s = fmt;
while (*s && ! (*s == ',' || *s == ':' || *s == ';') ) s++;
if (*s)
*s='\0';
else
last = 1 ;
v = atoi(fmt) ;
if (v > 0 && v < 1000000)
rate = v ;
else {
if (!strcmp(fmt, "ulaw")) format = AFMT_MU_LAW;
else if (!strcmp(fmt, "alaw")) format = AFMT_A_LAW;
else if (!strcmp(fmt, "u8")) format = AFMT_U8 ;
else if (!strcmp(fmt, "s16")) format = AFMT_S16_LE ;
else if (!strcmp(fmt, "mono")) stereo = 0;
else if (!strcmp(fmt, "stereo")) stereo = 1;
else if (!strcmp(fmt, "loop")) loop = 1;
else if (!strcmp(fmt, "rec")) play = 0;
else if (!strcmp(fmt, "play")) play = 1;
else if (!strcmp(fmt, "cd")) {
stereo = 1 ;
format = AFMT_S16_LE ;
rate = 44100;
}
}
if (last == 0) {
fmt = s+1;
goto again;
}
}
char buf[BLKSZ];
int
main(int argc, char *argv[])
{
int i,c;
int ac = argc;
char *p;
strcpy(dev, "/dev/audio");
while ( (c= getopt(argc, argv, "f:") ) != EOF ) {
switch (c) {
case 'f' :
if (optarg[0] >='0' && optarg[0] <='9')
sprintf(dev, "/dev/audio%s", optarg);
else
strcpy(dev, optarg);
break;
}
}
if (*argv[optind] == '+') {
parse_fmt(argv[optind]+1);
optind++;
}
/*
* assume a string with a "," and no "/" as a command
*/
if (strstr(argv[optind],",") && !strstr(argv[optind],"/") ) {
parse_fmt(argv[optind]);
optind++;
}
/*
* assume a string with a ":" and no "/" as a time limit
*/
if ( (p = strstr(argv[optind] , ":")) && !strstr(argv[optind],"/") ) {
*p = '\0';
size = parse_len(argv[optind]);
skip = parse_len(p+1);
optind++;
}
printf("Using device %s, speed %d, mode 0x%08x, %s\n",
dev, rate, format, stereo ? "stereo":"mono");
printf("using files: ");
for (i=optind; i< argc ; i++)
printf("[%d] %s, ",i, argv[i]);
printf("\n");
audiodev = open(dev, play ? 1 : 0);
if (audiodev < 0) {
printf("failed to open %d\n", dev);
exit(2);
}
ioctl(audiodev, SNDCTL_DSP_SETFMT, &format);
ioctl(audiodev, SNDCTL_DSP_STEREO, &stereo);
ioctl(audiodev, SNDCTL_DSP_SPEED, &rate);
printf("-- format %d,%s,0x%08x, len %d skip %d\n",
rate, stereo? "stereo":"mono",format, size, skip);
if (play) {
off_t ofs;
int limit;
again:
for (i=optind; i< argc ; i++) {
int l = -2;
int f = open(argv[i], O_RDONLY);
int sz ;
printf("opened %s returns %d\n", argv[i], f);
if (f < 0)
continue;
limit = size;
if (skip > 0) {
ofs = skip;
lseek(f, ofs, 0 /* begin */ );
}
sz = BLKSZ;
if (limit > 0 && limit < sz)
sz = limit ;
while ( (l = read(f, buf, sz) ) > 0 ) {
write(audiodev, buf, l);
if (limit > 0) {
limit -= l ;
if (limit > 0 && limit < sz)
sz = limit ;
if (limit <= 0 )
break;
if (limit < sz)
sz = limit ;
}
}
close(f);
}
if (loop)
goto again;
} else { /* record */
int l = -2;
int f ;
if (!strcmp(argv[optind], "-") )
f = 1;
else
f = open(argv[optind], O_WRONLY | O_CREAT | O_TRUNC, 0664);
fprintf(stderr,"open %s returns %d\n", argv[optind], f);
while ( size > 0 && (l = read(audiodev, buf, BLKSZ) ) > 0 ) {
if (l <= skip) {
skip -= l ; /* at most skip = 0 */
continue;
} else { /* l > skip */
l -= skip ;
if (l > size)
l = size ;
write(f, buf+skip, l);
skip = 0 ;
size -= l ;
}
}
close(f);
}
}

View File

@ -1,275 +0,0 @@
/*
* Sound interface for Speak Freely for Unix
*
* Designed and implemented in July of 1990 by John Walker
*
* FreeBSD / voxware version
*/
#define BUFL 8000
#include "speakfree.h"
#include <sys/dir.h>
#include <sys/file.h>
/* #include <math.h> */
#include <errno.h>
#include <sys/ioctl.h>
#ifdef LINUX
#include <linux/soundcard.h>
#else
#include <machine/soundcard.h>
#endif
#define AUDIO_MIN_GAIN 0
#define AUDIO_MAX_GAIN 255
static int abuf_size;
#define SoundFile "/dev/audio"
#define AUDIO_CTLDEV "/dev/mixer"
#define MAX_GAIN 100
struct sound_buf {
struct sound_buf *snext; /* Next sound buffer */
int sblen; /* Length of this sound buffer */
unsigned char sbtext[2]; /* Actual sampled sound */
};
/* Local variables */
static int audiof = -1; /* Audio device file descriptor */
static int Audio_fd; /* Audio control port */
struct sound_buf *sbchain = NULL, /* Sound buffer chain links */
*sbtail = NULL;
static int sbtotal = 0; /* Total sample bytes in memory */
static int playing = FALSE;/* Replay in progress ? */
/* static int playqsize; *//* Output queue size */
static int playlen = 0; /* Length left to play */
static unsigned char *playbuf = NULL; /* Current play pointer */
static int squelch = 0; /* Squelch value */
/* Convert local gain into device parameters */
static unsigned
scale_gain(unsigned g)
{
return (AUDIO_MIN_GAIN + (unsigned)
((int) ((((double) (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN)) *
((double) g / (double) MAX_GAIN)) + 0.5)));
}
#ifdef HALF_DUPLEX
static int oldvol = -1;
#endif
/*
* SOUNDINIT -- Open the sound peripheral and initialise for access. Return
* TRUE if successful, FALSE otherwise.
*/
int
soundinit(int iomode)
{
int attempts = 3;
assert(audiof == -1);
while (attempts-- > 0) {
if ((audiof = open(SoundFile, iomode)) >= 0) {
if ((Audio_fd = open(AUDIO_CTLDEV, O_RDWR)) < 0) {
perror(AUDIO_CTLDEV);
return FALSE;
}
/* fcntl(audiof, F_SETFL, O_NDELAY); */
#ifndef AUDIO_BLOCKING
if (ioctl(audiof, SNDCTL_DSP_NONBLOCK, NULL) < 0) {
perror("SNDCTL_DSP_NONBLOCK");
return FALSE;
}
if (ioctl(audiof, SNDCTL_DSP_GETBLKSIZE, &abuf_size) < 0) {
perror("SNDCTL_DSP_GETBLKSIZE");
return FALSE;
}
#endif
#ifdef HALF_DUPLEX
if (iomode == O_RDONLY) {
if (oldvol == -1)
oldvol = soundgetvol();
soundplayvol(0);
} else if (iomode == O_WRONLY && oldvol != -1 ) {
if (soundgetvol() == 0)
soundplayvol(oldvol);
oldvol = -1;
}
#endif
return TRUE;
}
if (errno != EINTR)
break;
fprintf(stderr, "Audio open: retrying EINTR attempt %d\n", attempts);
}
return FALSE;
}
/* SOUNDTERM -- Close the sound device. chan=1 for play, 2:capture */
void
soundterm(int chan)
{
if (audiof >= 0) {
int arg;
#ifdef AIOSTOP /* FreeBSD */
if (chan == 2) {
arg = AIOSYNC_CAPTURE;
ioctl(audiof, AIOSTOP, &arg);
}
#endif
#ifdef SNDCTL_DSP_SYNC
if (chan == 1)
ioctl(audiof, SNDCTL_DSP_SYNC);
#endif
#ifdef HALF_DUPLEX
if (oldvol != -1) {
if (soundgetvol() == 0)
soundplayvol(oldvol);
oldvol = -1;
}
#endif
if (close(audiof) < 0)
perror("closing audio device");
if (close(Audio_fd) < 0)
perror("closing audio control device");
audiof = -1;
}
}
/* SOUNDPLAY -- Begin playing a sound. */
void
soundplay(int len, unsigned char *buf)
{
int ios;
assert(audiof != -1);
while (TRUE) {
ios = write(audiof, buf, len);
if (ios == -1)
sf_usleep(100000);
else {
if (ios < len) {
buf += ios;
len -= ios;
} else
break;
}
}
}
/* SOUNDPLAYVOL -- Set playback volume from 0 (silence) to 100 (full on). */
void
soundplayvol(int value)
{
int arg;
arg = (value << 8) | value;
if (ioctl(Audio_fd, SOUND_MIXER_WRITE_PCM, &arg) < 0)
perror("SOUND_MIXER_WRITE_PCM");
}
#ifdef HALF_DUPLEX
/* SOUNDGETVOL -- Get current playback volume. */
int
soundgetvol()
{
int arg, v1, v2;
if (ioctl(Audio_fd, SOUND_MIXER_READ_PCM, &arg) < 0) {
perror("SOUND_MIXER_READ_PCM");
return -1;
}
v1 = arg & 0xFF;
v2 = (arg >> 8) & 0xFF;
return (v1 > v2) ? v1 : v2;
}
#endif
/* SOUNDRECGAIN -- Set recording gain from 0 (minimum) to 100 (maximum). */
void
soundrecgain(int value)
{
int arg;
arg = (value << 8) | value;
if (ioctl(Audio_fd, SOUND_MIXER_WRITE_RECLEV, &arg) < 0)
perror("SOUND_MIXER_WRITE_RECLEV");
}
/*
* SOUNDDEST -- Set destination for generated sound. If "where" is 0,
* sound goes to the built-in speaker; if 1, to the audio output jack.
*/
void
sounddest(int where)
{
}
/* SOUNDGRAB -- Return audio information in the record queue. */
int
soundgrab(char *buf, int len)
{
long read_size;
int c;
read_size = len;
#ifndef AUDIO_BLOCKING
if (read_size > abuf_size) {
read_size = abuf_size;
}
#endif
while (TRUE) {
c = read(audiof, buf, read_size);
if (c < 0) {
if (errno == EINTR) {
continue;
} else if (errno == EAGAIN) {
c = 0;
}
}
break;
}
if (c < 0) {
perror("soundgrab");
}
return c;
}
/* SOUNDFLUSH -- Flush any queued sound. */
void
soundflush()
{
char sb[BUFL];
int c;
#ifndef AUDIO_BLOCKING
while (TRUE) {
c = read(audiof, sb, BUFL < abuf_size ? BUFL : abuf_size);
if (c < 0 && errno == EAGAIN)
c = 0;
if (c < 0)
perror("soundflush");
if (c <= 0)
break;
}
#endif
}

View File

@ -1,84 +0,0 @@
/*
* test.c -- a simple utility for testing audio I/O
*
* (C) Luigi Rizzo 1997
*
* This code mmaps the io descriptor, then every second dumps the
* relevant data structures.
*
* call it as "test unit" where unit is the unit number you want
* to see displayed.
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/select.h>
caddr_t r, w, d;
#include </sys/i386/isa/snd/sound.h>
void
print_d(u_long *p, int unit)
{
snddev_info *d;
int i;
for (i=0; i<2000; i++) {
d = (snddev_info *)&(p[i]);
if ( d->magic == MAGIC(unit) )
break;
}
if (i == 2000) {
printf("desc not found\n");
return;
}
printf("device type %d name %s\n", d->type, d->name);
for (i=0;;i++) {
if (i%20 == 0)
printf("flags... fmt speed .bsz. c in-rl:in-dl:in-fl.ints "
" c ou-fl:ou_dl:ou-rl.ints |\n");
printf("%08x %3x %5d %5d %d %5d %5d %5d %4d %d %5d %5d %5d %4d |\n",
d->flags, d->play_fmt, d->play_speed, d->play_blocksize,
d->dbuf_in.chan,
d->dbuf_in.rl,
d->dbuf_in.dl,
d->dbuf_in.fl,
d->dbuf_in.int_count,
d->dbuf_out.chan,
d->dbuf_out.fl,
d->dbuf_out.dl,
d->dbuf_out.rl,
d->dbuf_out.int_count);
sleep(1);
}
}
main(int argc, char *argv[])
{
int fd ;
int unit = 0;
char devn[64];
if (argc>1) unit=atoi(argv[1]);
sprintf(devn,"/dev/mixer%d", unit);
fd = open (devn, O_RDWR);
printf("open returns %d\n", fd);
w = mmap(NULL, 0x10000, PROT_READ, 0, fd, 0); /* play */
r = mmap(NULL, 0x10000, PROT_READ, 0, fd, 1<<24); /* rec */
d = mmap(NULL, 0x2000, PROT_READ, 0, fd, 2<<24); /* desc */
printf("mmap: w 0x%08lx, r 0x%08lx, d 0x%08lx\n", w, r, d);
if (d && (int)d != -1 ) {
print_d((u_long *)d, unit);
}
if (w && (int)w != -1) munmap(w, 0x10000);
if (r && (int)r != -1) munmap(r, 0x10000);
if (d && (int)d != -1) munmap(d, 0x2000);
return 0;
}