#pragma once // The audio class, and the engine above it. // // A program that wants to make a sound should be able to open a device, write // samples to it, and stop -- which is what UNIX audio has always been, and // what this is. `/dev/dsp`, PCM in, `write()`, and a handful of ioctls to say // what the samples are. No server, no session bus, no protocol. // // The shape is the display class's, for the same reason: a consumer asks for // "the audio device" and gets whatever is bound, so adding a second driver is // not an edit to every caller. Found with device_find_class(DEVCLASS_AUDIO). // // --------------------------------------------------------------------------- // // **What the engine is for, and what it is not yet.** // // AudioEngine owns everything above a driver that is not specific to a chip: // what format the stream is in, how many bytes the hardware can take, and what // a write() does when it cannot take them all. That is genuinely shared -- a // second driver should not re-implement "block until there is room". // // It is deliberately *one stream*. Mixing several programs into one device is // a real feature with a real design behind it, and building it before anything // plays a single sound would be the mistake docs/plan/1.0.md warns about: work // that exists and is connected to nothing. The second program that wants to // play at the same time is what should cause it to be written. struct device; // Interleaved signed 16-bit little-endian, which is what every PC audio chip // takes natively and what a WAV file already contains. Others exist and none // of them is needed to make a sound; converting in the kernel to satisfy a // caller that could have converted first is not an obvious trade. #define AUDIO_FMT_S16LE 16 struct audio_format { unsigned int rate; // frames per second -- 48000, 44100 unsigned int channels; // 1 mono, 2 stereo unsigned int bits; // AUDIO_FMT_S16LE }; struct audio_ops { // Take this format, or report what the hardware will do instead by writing // back into `f` and returning 0. Returning negative means it cannot play at // all. A driver that only does 48 kHz stereo says so here rather than // failing a write() later, which is the difference between a program that // adapts and a program that gets silence. int (*configure)(struct device *, struct audio_format *f); // Bytes that can be queued right now without waiting. The engine uses this // to decide whether a write() has to block, so a driver that returns a // hardware-accurate number is a driver whose write() latency is honest. int (*space)(struct device *); // Queue PCM. Returns bytes accepted, which may be fewer than offered and // may be zero; the engine handles the remainder. Never blocks. int (*queue)(struct device *, const void *pcm, int bytes); // Start the stream if it is not running. Called by the engine once there is // something to play, rather than at open: a device left running with an // empty ring plays whatever was in it, which on real hardware is a loop of // the last buffer and sounds exactly as bad as it reads. void (*start)(struct device *); // Stop, and discard anything queued. void (*stop)(struct device *); // Bytes still to be played. Zero means the hardware has finished. The // engine polls this for SNDCTL_DSP_SYNC rather than a driver growing its // own wait loop. int (*pending)(struct device *); }; // --- what the kernel uses -------------------------------------------------- struct device *audio_get(void); int audio_configure(struct audio_format *f); // 0, or negative int audio_write(const void *pcm, int bytes); // bytes queued, or negative void audio_drain(void); void audio_stop(void); int dsp_writable(void); // poll(): room for a write // --- what userspace uses --------------------------------------------------- // // OSS numbering, which is the one every UNIX audio program already knows and // the only reason to invent a different set would be to make ports harder. // These four are the whole interface a tone generator or a WAV player needs. #define SNDCTL_DSP_SPEED 0xC0045002 // int in/out: sample rate #define SNDCTL_DSP_CHANNELS 0xC0045006 // int in/out: 1 or 2 #define SNDCTL_DSP_SETFMT 0xC0045005 // int in/out: AFMT_S16_LE #define SNDCTL_DSP_SYNC 0x00005001 // wait for the queue to drain #define SNDCTL_DSP_GETOSPACE 0x8010500C // struct audio_buf_info out #define SNDCTL_DSP_RESET 0x00005000 // stop, discard // The rest of what a buffer-and-pointer client needs -- ALSA's OSS plugin, which // is how SDL and everything else that speaks ALSA reaches this device. #define SNDCTL_DSP_SETFRAGMENT 0xC004500A // int in/out: count<<16 | log2(size) #define SNDCTL_DSP_GETFMTS 0x8004500B // int out: AFMT_* mask #define SNDCTL_DSP_GETCAPS 0x8004500F // int out: DSP_CAP_* #define SNDCTL_DSP_SETTRIGGER 0x40045010 // int in: PCM_ENABLE_OUTPUT or 0 #define SNDCTL_DSP_GETOPTR 0x800C5012 // struct count_info out #define SNDCTL_DSP_GETODELAY 0x80045017 // int out: bytes not yet heard #define DSP_CAP_TRIGGER 0x00001000 #define PCM_ENABLE_OUTPUT 0x00000002 struct count_info { int bytes; // played since the last reset int blocks; // fragments played since the last GETOPTR int ptr; // where playback is in a ring of fragsize * fragments }; #define AFMT_S16_LE 0x00000010 struct audio_buf_info { int fragments; // how many whole fragments are free int fragstotal; // how many there are in total int fragsize; // bytes in one int bytes; // free bytes, which is the number that matters };